Python使用apply_async和imap时遇到的区别
Contents
map/map_async与imap/imap_unordered的区别
- 传递iterable object的方式
- map会把发给他的所有任务iterable转成list,然后分块发给pool中的process。转换成list+分解成块需要把整个list放内存,占空间但是快
- imap不会转list到内存,默认不分块,每次发送一个iterable到process。这样占用少可以避免内存超额的问题,但是速度降低(可通过手动设置chunksize缓解)chunksize原理
- 它们返回结果的方式
- map必须执行完返回结果,map_async会立即返回AsyncResult但不是实际完成的值
- imap和imap_unordered会立即返回结果,因此可以用于tqdm。tqdm操作方法
- imap和imap_unordered
unordered会比imap稍好,先自动执行小任务出结果,同时总体上内存小一点
和apply/apply_async的区别
- apply仅将一个任务发送给process,在完成前都是block
- apply_async可以立即获得AsyncResult,任务结束得到值,可以用于tqdm。(tqdm操作方法)
apply is implemented by simply calling apply_async(…).get()
使用中遇到的问题
- 若希望在multi processing时用tqdm显示进度,不能用同步的map或apply,会阻塞tqdm更新
- 尽量显式传参给子进程
- 虽然unix的fork方式可以让子进程使用父进程的全局变量,但仍建议传参
- 原因:Windows兼容、别处也可直接调用、防止子进程alive时资源GC
- 有关Windows/Interactive环境下执行
- 开启多进程有fork/spawn/fork_server三种方式 (官方文档),windows和interactive的shell采用spawn,会很难从头开始导入code。而unix使用fork会直接复制running states,就不会有这个问题。详见
- 因此,Windows下需要判断
if __name__ == '__main__':
后再执行imap等开启多进程,称为protect the main function (否则会导致main中内容又执行一遍),官方文档的programming guidline也提示safe importing of main module - 但是,有时children需要__main__模块的内容才能正确运行,而Windows下子进程的__name__不再是__main__ 参考,因此需要用到的资源要在
if __name__ == '__main__':
之前确定好,在这语句之后的值不会传递到子进程里。interactive interpreter(导入code困难)有时会无法运行 可以参考此节的的Note - 为此有人会专门把函数写到文件,用多进程时import它,以避免spawn的导入code失败(此处的jupyter会失败,我现在没有遇到此问题 参考)
- jupyter中的可用性十分不稳定。multiprocessing会把整个jupyter的kernel复制一遍,有时会遇到谜之bug(比如notebook前端一直收到克隆kernel的更新状态失败信息)。而就算把上述best practice都做了有时也会只有第一个循环的进程跑不完,看gdb是其他进程read的时候卡住,第一个循环的进程有futex锁,整个程序卡死。
一个例子,jupyterlab有时可以用
|
|
为什么要用multiprocessing
cpython的GIL(Global Interpreter Lock)默认任何时候单进程只有一个线程能拿到GIL,竞争、切换锁消耗资源,且永远只能同时执行一个线程,效率不高。多核时,其他核心的线程虽然被唤醒-竞争锁,但大概率还是CPU0拿到锁,其他线程回到待调度,造成thrashing,效率更低。
不过,IO密集操作的线程等待时间较长,这时切换到其他线程就可以提高效率。CPU密集则需要multiprocessing
参考1 参考2 参考3