Python线程用于预导入模块
我正在写一个用于科学计算的Python应用程序。目前,当用户在图形界面(GUI)中开始新的物理模拟时,程序会立刻导入几个必要的模块,比如Traits
和Mayavi
。这些模块比较大,导入起来需要花费不少时间,用户得等大约10秒才能继续,这样体验不好。
我想到了一种可能的解决办法。我来描述一下,也许有人已经实现过,如果有的话请给我个链接。如果没有,我可能会自己动手做。
我想要一个单独的线程来异步导入这些模块。这个线程可能会是threading.Thread
的一个子类。
下面是一个使用示例:
importer_thread = ImporterThread()
importer_thread.start()
# ...
importer_thread.import('Mayavi')
importer_thread.import('Traits')
# A thread-safe method that will put the module name
# into a queue which the thread in an inifine loop
# ...
# When the user actually needs the modules:
import Mayavi, Traits
# If they were already loaded by importer_thread, we're good.
# If not, we'll just have to wait as usual.
你知道有没有类似的东西吗?如果没有,你对设计有什么建议吗?
4 个回答
总体来说,这个想法不错,但在Python和图形界面(GUI)运行的时候,如果后台线程在不停地加载东西,可能会导致界面反应不灵敏。因为在Python中,加载模块(import
)这个过程本身就会占用很多资源,不仅仅是因为全局解释器锁(GIL),还有其他一些特定的锁机制。
不过,尝试一下还是值得的,因为这样可能会让事情变得稍微好一些。而且这也很简单,因为队列(Queue
)本身就是线程安全的,除了队列的放入(put
)和取出(get
)操作外,你基本上只需要用到一个__import__
。不过,如果效果不明显,你可能还需要其他的解决办法。
如果你有一个速度非常快但空间有限的存储设备,比如“内存驱动器”或者特别快速的固态硬盘,考虑把需要的包放在一个.tar.bz2
(或者其他格式的压缩包)里,然后在程序启动时解压到快速的驱动器上。这基本上就是输入输出操作(I/O),所以不会严重影响性能,因为I/O操作会迅速释放全局解释器锁(GIL),而且这也特别容易,可以交给一个子进程来运行tar xjf
之类的命令。
如果加载慢的原因是因为有大量的.py/.pyc/.pyo
文件,可以尝试把这些文件(只保留.pyc
格式,不要.py
)放在一个压缩文件里,然后从那里加载(不过这只会帮助减少I/O的开销,具体效果还要看你的操作系统、文件系统和存储设备:对于加载大型DLL文件或在加载时执行初始化代码的延迟,这种方法可能没有太大帮助,我猜这些更可能是导致慢的原因)。
你还可以考虑使用multiprocessing
来把应用程序拆分开,同样使用队列(但要用多进程的那种)来进行通信,这样可以把模块加载和一些重计算任务分配给几个辅助进程,从而实现异步处理(这也可能帮助充分利用多个核心)。不过,我觉得对于可视化任务(比如你可能在用mayavi做的事情)来说,这可能会比较难以安排,但如果你还有一些“纯重计算”的包和任务,这可能会有所帮助。
这个问题在于,导入的模块必须在使用之前完成。如果它们第一次被用到的时间不对,应用程序可能还是需要等待10秒才能启动。更有效的方法是分析这些模块,找出它们为什么导入这么慢。
为什么不在应用启动的时候就直接这样做呢?
def background_imports():
import Traits
import Mayavi
thread = threading.Thread(target=background_imports)
thread.setDaemon(True)
thread.start()