局部变量未以与 Python 中的共享内存对象相同的方式在循环中更新

Local variable not updated in a loop in the same way as shared memory objects in Python

在下面的Python代码中,多处理模块启动了三个进程,打印出一个局部变量和两个多处理共享内存对象的值。

import multiprocessing as mp
import os,time

# local variable
count = 0

# shared memory objects (int and array)
scalar = mp.Value('i', 0)
vector = mp.Array('d', 3)

def showdata(label, val, arr):
    print(label, "==> PID:", os.getpid(), ", count:", count, ", int:", val.value, ", arr:", list(arr))

ps = []
for i in range(3):
    count += 1
    scalar.value += 1
    vector[i] += 1
    p=mp.Process(target=showdata, args=(('process %s' % i), scalar, vector))
    p.start()
    ps.append(p)
    # time.sleep(.1)

# block the main thread until all processes have finished...
for p in ps:
    p.join()

此代码的输出如下...

process 0 ==> PID: 35499 , count: 1 , int: 3 , arr: [1.0, 1.0, 1.0]
process 1 ==> PID: 35500 , count: 2 , int: 3 , arr: [1.0, 1.0, 1.0]
process 2 ==> PID: 35501 , count: 3 , int: 3 , arr: [1.0, 1.0, 1.0]

如果我通过取消注释 time.sleep(0.1) 对象来更改代码以添加延迟,则输出将更改为以下内容:

process 0 ==> PID: 35499 , count: 1 , int: 1 , arr: [1.0, 0.0, 0.0]
process 1 ==> PID: 35500 , count: 2 , int: 2 , arr: [1.0, 1.0, 0.0]
process 2 ==> PID: 35501 , count: 3 , int: 3 , arr: [1.0, 1.0, 1.0]

有意义的是,没有任何延迟(即上面的第一个输出)共享内存对象对于所有三个进程来说都是相同的值,因为一旦它们启动,"for" 循环就会迅速完成并更新在单独的进程可以 运行 它们的目标 "showdata" 函数之前共享对象的值。

但是,我不明白为什么本地 "count" 变量被允许增量更新。我希望它像共享内存对象一样被对待,在没有任何延迟的情况下,计数将在单独进程中的 "showdata" 函数 运行 之前快速增加三倍。按照这个逻辑,"count" 三个进程的值都应该是 3。

谁能解释为什么没有发生这种情况?

这是 运行ning 在 Python 3.4.3 在 OS X 10.10.3.

我相信这是因为 mp.Process 启动了一个新进程(请注意这不是同一进程的新线程,它是一个具有自己的 PID 的全新进程,如您在输出中所见) 对于每个函数,每个进程都有自己的内存 (stack/heap)。局部变量存储在每个进程自己的内存中,因此当调用该特定进程时,它会访问自己的堆栈,其中包含进程启动时存在的 count

但是对于由多处理线程创建的共享内存,它们在 multiprocessing.Process 和父进程派生的每个子进程之间共享。

您从代码中看到的不同行为是因为它具有竞争条件。竞争发生在更新共享内存值的主进程和打印出这些值的每个子进程之间。根据代码各个部分的时间安排,您可能会得到几种不同的结果中的任何一种(您展示的两个是极端情况,可能是最容易获得的结果,但对于某些但不是所有子流程来说并非不可能查看仅部分更新的共享数据)。尝试在 showdata 函数中添加一个随机延迟,您可能会得到更多变化。

"local" 变量(实际上是一个全局变量,但这并不重要)的行为方式不同的原因是它作为 [=] 的一部分被复制到每个子进程内存中11=] 呼叫。那里没有竞争条件,因为父进程不可能 运行 提前并在子进程收到其副本之前再次更改值。

非共享变量在您创建 新进程时被复制,因此 for 循环不会继续,直到完成此复制。