我正在尝试在进程之间共享一个锁。我知道共享锁的方法是将它作为参数传递给目标函数。但是我发现即使是下面的方法也是有效的。我无法理解进程共享此锁的方式。有人可以解释一下吗?
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()
答案 0 :(得分:4)
在Windows上(你说你正在使用),这些事情总是会减少multiprocessing
如何与pickle
一起使用的细节,因为所有跨越Windows上的流程边界的Python数据都是通过在发送端进行酸洗(并在接收端进行取消)。
我最好的建议是避免做一些提出这些问题的事情开始;-)例如,你展示的代码在Python 2下在Windows上爆炸,如果使用{{1 }}方法而不是multiprocessing.Pool
。
这不只是锁定,只是试图挑选绑定方法(如multiprocessing.Process
)在Python 2中爆炸。想一想。您正在越过进程边界,并且不是接收端上与self.run_job
对应的对象。 {(1}}应该在接收端绑定哪个对象?
在Python 3中,pickle self
也挑选self.run_job
对象的副本。这就是答案:对应于self.run_job
的{{1}}对象是由接收端的魔法创建的。清除泥土。 self
整个州被腌制,包括SampleClass
。这就是它“有效”的原因。
有关更多实施细节,请参阅此处:
Why can I pass an instance method to multiprocessing.Process, but not a multiprocessing.Pool?
从长远来看,如果你坚持使用显然有用的东西,你将遭受最少的谜团:传递模块全局可调用对象(例如,实例方法和本地函数),并显式传递{{ 1}}数据对象(是self
,t
,t.lock
的实例等等。)
答案 1 :(得分:2)
在Unix操作系统上,通过fork
原语创建新进程。
fork
原语通过克隆将其分配给子进程的父进程内存地址空间来工作。孩子将拥有父母的内存副本以及文件描述符和共享对象的副本。
这意味着,当你调用fork时,如果父文件已打开,那么孩子也会拥有它。这同样适用于共享对象,如管道,套接字等...
在Unix + CPython中,Locks
通过sem_open
原语实现,该原语在分叉进程时被设计为shared。
我通常建议不要混合并发(特别是多处理)和OOP,因为它经常导致这种误解。
编辑:
刚才看到你正在使用Windows。 Tim Peters给出了正确的答案。为了抽象,Python试图通过其API提供与OS无关的行为。当调用实例方法时,它将pickle对象并通过管道发送它。从而提供与Unix类似的行为。
我建议您阅读programming guidelines进行多处理。特别是在第一点上解决了您的问题:
避免共享状态
应尽可能避免在流程之间转移大量数据。
最好坚持使用队列或管道进行进程之间的通信,而不是使用较低级别的同步原语。