Python 多处理中进程的生命周期是怎样的?

问题描述 投票:0回答:1

在普通的Python代码中,我可以理解进程的生命周期。例如执行

python script.py
时:

  1. shell收到命令
    python script.py
    ,os创建一个新进程开始执行
    python
    .
  2. python
    可执行文件设置解释器,并开始执行
    script.py
  3. script.py
    执行完成时,python解释器将退出。

在多处理的情况下,我很好奇其他进程会发生什么。

以下面的代码为例:

# test.py
import multiprocessing

# Function to compute square of a number
def compute_square(number):
    print(f'The square of {number} is {number * number}')

if __name__ == '__main__':
    # List of numbers
    numbers = [1, 2, 3, 4, 5]
    
    # Create a list to keep all processes
    processes = []
    
    # Create a process for each number to compute its square
    for number in numbers:
        process = multiprocessing.Process(target=compute_square, args=(number,))
        processes.append(process)
        process.start()
    
    # Ensure all processes have finished execution
    for process in processes:
        process.join()

    print("All processes have finished execution.")

当我执行

python test.py
时,我知道
test.py
将作为
__main__
模块执行。但是其他进程会发生什么情况呢?

具体来说,当我执行

multiprocessing.Process(target=compute_square, args=(number,)).start()
时,该过程会发生什么?

该进程如何调用Python解释器?如果它只是

python script.py
,它如何知道需要执行名为
compute_square
的函数?或者它使用
python -i
,并通过管道传递命令来执行?

python multiprocessing python-multiprocessing
1个回答
0
投票

根据 multiprocessing 模块的

Python 文档
,用于创建进程的底层系统功能取决于平台,具有 3 种不同的“启动方法”:
spawn
fork
forkserver 
.

默认使用哪一个取决于平台,尽管您可以使用

multiprocessing.set_start_method()
自行选择启动方法,并将方法名称作为字符串。

当您在 POSIX 系统上使用

fork()
时,子进程几乎是其父进程的克隆 – 当然除了它的 PID 和其他必要的差异。它从内存中的同一点运行相同的代码,虽然它们的内存页面最初是共享的,但在写入任何共享页面时,会为每个进程创建一个专用副本。在我看来,这是最容易理解的模型,只需将新进程视为初始进程的完整副本,除了它知道它是子进程(而不是父进程),并决定运行结果请求的功能。

如果您不熟悉

fork()
,我鼓励您阅读它,也许从 维基百科文章开始,然后是 手册页

spawn
还有一篇 维基百科文章 手册页

对于 Python,我们可以使用一个简单的程序来测试所有 3 个启动方法,该程序将

spawn
fork
forkserver
作为第一个参数:

from multiprocessing import Process, set_start_method
import os
import random
import sys

# generate a random value in the parent process
r = random.randint(0, int(1e9))

def info(msg):
    print(f'pid: {os.getpid()}, ppid: {os.getppid()}, module: {__name__}, msg: {msg}')

def f(arg):
    info(f'[child] arg: {arg}, r: {r}')

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print(f'Usage: {sys.argv[0]} spawn|fork|forkserver')
        sys.exit(1)

    method = sys.argv[1]
    print(f'setting start method: {method}')
    set_start_method(method)

    info(f'[parent] r: {r}')
    p = Process(target=f, args=('hello',))
    p.start()
    p.join()

当使用

spawn
时,将创建一个新的解释器并调用进程的入口点函数。看看随机变量
r
是如何不被保留的:

setting start method: spawn
pid: 44658, ppid: 1356, module: __main__, msg: [parent] r: 242489315
pid: 44688, ppid: 44658, module: __mp_main__, msg: [child] arg: hello, r: 229487814

使用

fork
,我们会在调用
p.start()
的位置克隆进程,因此我们仍然会看到
r
的相同值:

setting start method: fork
pid: 49721, ppid: 1356, module: __main__, msg: [parent] r: 376097656
pid: 49738, ppid: 49721, module: __main__, msg: [child] arg: hello, r: 376097656

对于

forkserver
,我本希望多个进程继承相同的
r
值,但情况似乎并非如此:

setting start method: forkserver
pid: 66317, ppid: 1356, module: __main__, msg: [parent] r: 917735863
pid: 66336, ppid: 66334, module: __mp_main__, msg: [child] arg: hello, r: 698876823

在第一个进程之后启动和停止第二个进程不会为其提供与根进程或第一个子进程相同的

r
值,这表明子进程派生的服务器进程甚至可能已经启动早于脚本开始。

© www.soinside.com 2019 - 2024. All rights reserved.