本地变量未在循环中更新,其方式与Python中的共享内存对象相同

时间:2015-06-13 07:09:23

标签: python multiprocessing python-multiprocessing

在下面的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。

任何人都可以解释为什么没有发生这种情况吗?

这是在OS X 10.10.3中的Python 3.4.3中运行。

3 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

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

“本地”变量(实际上是一个全局变量,但这并不重要)的行为方式不同的原因是它作为mp.Process调用的一部分被复制到每个子进程内存中。那里没有竞争条件,因为父进程不可能在子进程收到副本之前再次运行并再次更改值。

答案 2 :(得分:1)

创建新进程时会复制非共享变量,因此在完成此副本之前for循环不会继续。