Python: 为什么这里进程间共享多进程锁?

Python: Why is the multiprocessing lock shared among processes here?

我正在尝试在进程之间共享一个锁。我知道共享锁的方法是将它作为参数传递给目标函数。但是我发现即使是下面的方法也有效。我无法理解进程共享此锁的方式。谁能解释一下?

import multiprocessing as mp
import time


class SampleClass:

    def __init__(self):
        self.lock = mp.Lock()
        self.jobs = []
        self.total_jobs = 10

    def test_run(self):
        for i in range(self.total_jobs):
            p = mp.Process(target=self.run_job, args=(i,))
            p.start()
            self.jobs.append(p)

        for p in self.jobs:
            p.join()

    def run_job(self, i):
        with self.lock:
            print('Sleeping in process {}'.format(i))
            time.sleep(5)


if __name__ == '__main__':
    t = SampleClass()
    t.test_run()

在 Unix 操作系统上,新进程是通过 fork 原语创建的。

fork 原语通过克隆 parent 进程内存地址 space 并将其分配给 child 来工作。 child 将拥有 parent 的内存副本以及文件描述符和共享的 objects。

这意味着,当您调用 fork 时,如果 parent 已经打开了一个文件,那么 child 也会打开它。这同样适用于共享 objects,例如管道、套接字等...

在 Unix+CPython 中,Locks 是通过 sem_open 原语实现的,该原语被设计为 shared 在 fork 进程时。

我通常建议不要混合并发(尤其是多处理)和 OOP,因为它经常会导致此类误解。

编辑:

刚才看到您正在使用Windows。 Tim Peters 给出了正确答案。为了抽象,Python 试图在其 API 上提供 OS 独立行为。调用实例方法时,它会 pickle object 并通过管道发送。因此提供了与 Unix 类似的行为。

我建议您阅读有关多处理的 programming guidelines。您的问题在第一点特别得到解决:

Avoid shared state

As far as possible one should try to avoid shifting large amounts of data between processes.

It is probably best to stick to using queues or pipes for communication between processes rather than using the lower level synchronization primitives.

在 Windows(你说你正在使用)上,这些事情总是简化为 multiprocessing 如何与 pickle 一起玩的细节,因为所有 Python Windows 上的数据跨越进程边界是通过在发送端进行 pickling(并在接收端进行 unpickling)来实现的。

我最好的建议是避免做会引发此类问题的事情;-) 例如,您显示的代码在 Python 2 下的 Windows 上爆炸,并且也爆炸在 Python 3 下,如果您使用 multiprocessing.Pool 方法而不是 multiprocessing.Process.

不仅仅是锁,简单地尝试 pickle 绑定方法(如 self.run_job)在 Python 中爆炸 2. 考虑一下。您正在跨越进程边界,并且 没有 对应于接收端 self 的对象。 self.run_job 应该在接收端绑定到什么对象?

在Python 3 中,酸洗self.run_job 酸洗self 对象的副本。这就是答案:对应于 selfSampleClass 对象是在接收端通过魔法创建的。清澈如泥。 t 的整个状态都被 pickled,包括 t.lock。这就是为什么 "works"。

有关更多实施细节,请参阅此内容:

Why can I pass an instance method to multiprocessing.Process, but not a multiprocessing.Pool?

在漫长的 运行 中,如果您坚持显然旨在工作的事情,您将遇到最少的谜团:传递模块全局可调用对象(例如,实例方法或局部函数) ,并显式传递 multiprocessing 数据对象(无论是 LockQueuemanager.list 等的实例)。