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
对象的副本。这就是答案:对应于 self
的 SampleClass
对象是在接收端通过魔法创建的。清澈如泥。 t
的整个状态都被 pickled,包括 t.lock
。这就是为什么 "works"。
有关更多实施细节,请参阅此内容:
Why can I pass an instance method to multiprocessing.Process, but not a multiprocessing.Pool?
在漫长的 运行 中,如果您坚持显然旨在工作的事情,您将遇到最少的谜团:传递模块全局可调用对象(例如,实例方法或局部函数) ,并显式传递 multiprocessing
数据对象(无论是 Lock
、Queue
、manager.list
等的实例)。
我正在尝试在进程之间共享一个锁。我知道共享锁的方法是将它作为参数传递给目标函数。但是我发现即使是下面的方法也有效。我无法理解进程共享此锁的方式。谁能解释一下?
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
对象的副本。这就是答案:对应于 self
的 SampleClass
对象是在接收端通过魔法创建的。清澈如泥。 t
的整个状态都被 pickled,包括 t.lock
。这就是为什么 "works"。
有关更多实施细节,请参阅此内容:
Why can I pass an instance method to multiprocessing.Process, but not a multiprocessing.Pool?
在漫长的 运行 中,如果您坚持显然旨在工作的事情,您将遇到最少的谜团:传递模块全局可调用对象(例如,实例方法或局部函数) ,并显式传递 multiprocessing
数据对象(无论是 Lock
、Queue
、manager.list
等的实例)。