python多进程,multiprocessing和fork

最近使用python进行多进程开发,遇到问题:multiprocessing.Process的实例化必须放在if __name__ == "__main__"代码块或自定义函数中,这个问题涉及到以下知识点:

__main__的作用、multiprocessing在windows下实现多进程的过程、unix下fork的原理,下面逐一解答。

先科普下进程的定义:程序的一次动态执行过程,包括代码段、数据段、堆栈段、一组寄存器和内核状态,是操作系统中资源调度和任务分配的基本/独立单位。有别于线程,线程共享进程内部的资源。

形象化解释:windows下的进程(正在运行的程序)如下所示,此外,一个进程可以创建多个子进程,但一个子进程只对应一个父进程。

1、if  __name__ == "__main__"的作用

(1)运行当前的module(Py文件)时,因为__name__为"__main__",所以__main__下方的代码会被顺序执行到。

(2)通过__main__语句,当Py文件A import其他module(Py文件B),被import的module(PY文件B)执行时,__name__为被import的module的文件名(Py文件B的文件名),而不是"__main__",实现不执行__main__(Py文件B)下方的代码。【这个要特别注意!】

print(__name__)

假设有以下文件TEST.py文件,其他py文件import TEST模块后,__name__等于TEST,所以test函数不会被执行。

def test():
    print("ttt")

print(__name__)

if __name__ == '__main__':
    test()

2、multiprocessing在windows下实现多进程的过程

不同于unix/linux采用fork的方式产生子进程,windows没有fork功能,其通过重新加载当前模块的方式产生子进程(即自动import当前module文件)。【multiprocessing模块启动一个新的python进程,并导入当前调用模块(当前py文件)

如果 p = multiprocessing.Process(target=func, name='') 放置在module的可执行执行的区域(没有放置在 if __name__ == '__main__'语句块内,或没有放置在自定义函数内),那么新进程(即子进程)会重复执行p = multiprocessing.Process(target=func, name='')语句,导致子进程再次产生子进程,而这种递归创建子进程的方式是不允许的,导致抛出如下错误:

RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

正确方式1:

正确方式2:

所以,window下创建子进程时,需要将创建子进程的代码块包裹在__main__代码块内或自定义函数内。

3、unix/linux下fork的原理

linux和unix通过fork的方式产生子进程,fork函数的原理是:拷贝当前进程(即父进程)作为子进程,相关知识点如下:

1)fork函数执行一次,返回2次返回值,即一次在父进程中返回产生的子进程的进程号pid;另一次在子进程中返回0(0标志当前进程是子进程)。

    python的os模块的fork函数,会自动fork出子进程,子进程共享父进程的代码段(通俗认为,子进程相当于父进程的clone),但数据段、堆栈段会拷贝给子进程,这样子父进程之间不会互相影响。

2)source、fork、exec:

fork:会clone父进程,产生一个子进程,子、父进程异步执行。

exec:不会fork出新进程,而是占用父进程,替换新的代码段、舍弃旧的数据段和堆栈段,开辟新的数据段和堆栈段,相当于在旧的进程内存里执行新的进程,旧进程被新进程所覆盖

source:不会fork出新进程,而是占用父进程的执行阶段(插入到父进程的执行流程中),先执行source后的脚本,后执行父进程,即source后的脚本代码影响父进程。source命令会根据具体情况调用以下exec函数的其中之一:execl、execv、execle、execve、execlp、execvp。

fork的应用场景:

1)直接fork出父进程的clone,子、父进程互不影响,异步执行。【网络服务器的场景】

2)先fork父进程,得到一个子进程,子进程在调用exec函数,将旧的子进程的代码段、数据段和堆栈段全部替换掉,得到一个真正新的子进程(pid不会变化),子、父进程异步执行。【shell脚本的场景】

fork的注意点:

使用pid = os.fork()方式,会clone父进程的代码段。在执行多进程时,在子进程的执行代码块(pid == 0)执行完毕之后,需要使用exit函数退出子进程,所以如果没有限制子进程执行代码,那么子进程会“原样”执行父进程的代码段,导致出错!

如下图所示(例子参考https://code-maven.com/parallel-processing-using-fork-in-python

exit()不注释掉时的报错信息:

-- over  --

猜你喜欢

转载自blog.csdn.net/qm5132/article/details/82999279
今日推荐