5

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

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()
4

2 回答 2

5

在 Windows 上(你说你正在使用),这些事情总是减少到关于如何使用的细节multiprocessingpickle因为 Windows 上所有跨越进程边界的 Python 数据都是通过在发送端进行酸洗(在接收端取消酸洗)来实现的.

我最好的建议是避免一开始就提出这样的问题 ;-) 例如,您显示的代码在 Python 2 下的 Windows 上会爆炸,如果您使用multiprocessing.Pool方法而不是multiprocessing.Process.

这不仅仅是锁,只是试图腌制一个绑定的方法(如self.run_job)在 Python 2 中爆炸。想想看。您正在跨越进程边界,并且在接收端没有对应的对象。接收端应该绑定self到什么对象?self.run_job

在 Python 3 中,酸洗self.run_job 还会腌制对象的副本self。所以这就是答案:SampleClass对应的对象self是在接收端通过魔法创建的。清如泥。 t的整个状态都是腌制的,包括t.lock. 这就是它“起作用”的原因。

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

为什么我可以将实例方法传递给 multiprocessing.Process,而不是 multiprocessing.Pool?

从长远来看,如果你坚持那些显然是为了工作的东西,你将遭受最少的谜团:传递模块全局可调用对象(既不是实例方法也不是局部函数),并显式传递multiprocessing数据对象(无论是Lock, Queue,manager.list等的实例)。

于 2017-07-11T17:45:33.700 回答
4

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

fork原语通过克隆将其分配给子进程的父进程内存地址空间来工作。子级将拥有父级内存以及文件描述符和共享对象的副本。

这意味着,当您调用 fork 时,如果父级打开了一个文件,则子级也将拥有它。这同样适用于共享对象,如管道、套接字等......

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

我通常建议不要混合并发(特别是多处理)和 OOP,因为它经常导致这种误解。

编辑:

刚才看到您正在使用 Windows。蒂姆·彼得斯给出了正确的答案。为了抽象起见,Python 试图通过其 API 提供与操作系统无关的行为。调用实例方法时,它将腌制对象并通过管道发送。因此提供了与 Unix 类似的行为。

我建议您阅读多处理的编程指南。您的问题特别在第一点得到解决:

避免共享状态

应尽可能避免在进程之间转移大量数据。

最好坚持使用队列或管道在进程之间进行通信,而不是使用较低级别的同步原语。

于 2017-07-11T16:28:54.307 回答