关于Python中多进程的问题
前言
最近为了提高我的人脸识别项目的效率,我决定使用多线程/进程,在这里谈谈心得。
一、多进程与多线程的区别
多任务可以由多进程完成,也可以由一个进程内的多线程完成。 进程是由若干线程组成的,一个进程至少有一个线程。 由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。
在这里找到一个关于多进程和多线程的不同之处的一个小栗子:
我们打个比方,假设你不幸正在准备中考,每天晚上需要做语文、数学、英语、物理、化学这5科的作业,每项作业耗时1小时。 如果你先花1小时做语文作业,做完了,再花1小时做数学作业,这样,依次全部做完,一共花5小时,这种方式称为单任务模型,或者批处理任务模型。 假设你打算切换到多任务模型,可以先做1分钟语文,再切换到数学作业,做1分钟,再切换到英语,以此类推,只要切换速度足够快,这种方式就和单核CPU执行多任务是一样的了,以幼儿园小朋友的眼光来看,你就正在同时写5科作业。 但是,切换作业是有代价的,比如从语文切到数学,要先收拾桌子上的语文书本、钢笔(这叫保存现场),然后,打开数学课本、找出圆规直尺(这叫准备新环境),才能开始做数学作业。操作系统在切换进程或者线程时也是一样的,它需要先保存当前执行的现场环境(CPU寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,但是也需要耗费时间。如果有几千个任务同时进行,操作系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。 所以,多任务一旦多到一个限度,就会消耗掉系统所有的资源,结果效率急剧下降,所有任务都做不好。
可见,多线程的本质执行速度与顺序执行并无差别。 在大佬的解释之下,我决定使用多进程,Python的多线程本质上并不能调动多个CPU的内核,相当于单个内核在不断切换线程,实际上测试也发现,时间所差无几。
二、Python下多进程的使用
1 | from multiprocessing import Process |
但是在主进程与子进程之间是相对独立,所以我们经常需要使用队列(Queue)_(队列是一种先进先出的数据结构,具体请百度)_来进行进程间通讯。
1 | from multiprocessing import Process,Queue |
上面的代码结果可以看到我们主进程中可以通过Queue获取子进程中put的数据,实现进程间的通信。
三、关于Windows下的多进程问题
为什么至今我的人脸识别程序未更新?当然是因为我鸽。就是Windows下的多进程问题难以实现。 查看官方文档:
16.6.3.2. Windows
Since Windows lacks
os.fork()
it has a few extra restrictions: More picklabilityEnsure that all arguments to
Process.__init__()
are picklable. This means, in particular, that bound or unbound methods cannot be used directly as thetarget
argument on Windows — just define a function and use that instead. Also, if you subclassProcess
then make sure that instances will be picklable when theProcess.start
method is called.Global variables
Bear in mind that if code run in a child process tries to access a global variable, then the value it sees (if any) may not be the same as the value in the parent process at the time that
Process.start
was called. However, global variables which are just module level constants cause no problems.Safe importing of main module
Make sure that the main module can be safely imported by a new Python interpreter without causing unintended side effects (such a starting a new process). For example, under Windows running the following module would fail with a
RuntimeError
:
1
2
3
4
5
6
7 from multiprocessing import Process
def foo():
print 'hello'
p = Process(target=foo)
p.start()Instead one should protect the “entry point” of the program by using
if __name__ == '__main__':
as follows:
1
2
3
4
5
6
7
8
9 from multiprocessing import Process, freeze_support
def foo():
print 'hello'
if __name__ == '__main__':
freeze_support()
p = Process(target=foo)
p.start()(The
freeze_support()
line can be omitted if the program will be run normally instead of frozen.) This allows the newly spawned Python interpreter to safely import the module and then run the module’sfoo()
function. Similar restrictions apply if a pool or manager is created in the main module.
简单来说,就是Windows下缺乏UNIX下的fork。 众所周知,查阅资料发现,python 的 Process 对象在执行 start 方法的时候,有三种方式,分别是 spawn, fork, forkserver。其中 unix 系统默认采用的是 fork 方式。而 windows 默认采用 spawn 方式,且缺少后面两种方法。 spawn 方法会开启一个全新的解释器进程,子进程只会从主进程继承那些对运行 process 的 run() 方法有必要的资源。 在 spawn 方法运行时,需要把主模块 __main__
作为模块 import 进去,这样主模块中的顶层代码都会执行一遍。 最最重要的是,参数的传递无法实现。意味着我将需要使用大量的进程间通讯将主进程中的参数传递到子进程中,这就是问题所在。
三、总结
无论是在Windows还是UNIX中,使用多线程/进程都需要丰富的操作系统知识,否则很有可能陷在一个莫名其妙的错误中无法自拔。