Python多处理锁

时间:2015-02-01 21:13:25

标签: python multiprocessing

此多处理代码按预期工作。它创建了4个Python进程,并使用它们打印数字0到39,每次打印后都会有延迟。

import multiprocessing
import time

def job(num):
  print num
  time.sleep(1)

pool = multiprocessing.Pool(4)

lst = range(40)
for i in lst:
  pool.apply_async(job, [i])

pool.close()
pool.join()

但是,当我尝试使用multiprocessing.Lock来防止多个进程打印到标准输出时,程序会立即退出而不会输出任何内容。

import multiprocessing
import time

def job(lock, num):
  lock.acquire()
  print num
  lock.release()
  time.sleep(1)

pool = multiprocessing.Pool(4)
l = multiprocessing.Lock()

lst = range(40)
for i in lst:
  pool.apply_async(job, [l, i])

pool.close()
pool.join()

为什么引入multiprocessing.Lock会使这段代码不起作用?

更新:当全局声明锁时(我在那里做了一些非权威性的测试以检查锁是否正常),而不是上面的代码将锁作为参数传递(Python的多处理文档显示锁)作为参数传递)。下面的代码有一个全局声明的锁,而不是在上面的代码中作为参数传递。

import multiprocessing
import time

l = multiprocessing.Lock()

def job(num):
  l.acquire()
  print num
  l.release()
  time.sleep(1)

pool = multiprocessing.Pool(4)

lst = range(40)
for i in lst:
  pool.apply_async(job, [i])

pool.close()
pool.join()

3 个答案:

答案 0 :(得分:11)

如果您将pool.apply_async更改为pool.apply,则会出现以下异常:

Traceback (most recent call last):
  File "p.py", line 15, in <module>
    pool.apply(job, [l, i])
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 244, in apply
    return self.apply_async(func, args, kwds).get()
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 558, in get
    raise self._value
RuntimeError: Lock objects should only be shared between processes through inheritance

pool.apply_async只是隐藏它。我讨厌这样说,但使用全局变量可能是你的例子最简单的方法。我们只希望velociraptors不会得到你。

答案 1 :(得分:3)

我认为原因是多处理池使用pickle在进程之间传输对象。但是,Lock无法腌制:

>>> import multiprocessing
>>> import pickle
>>> lock = multiprocessing.Lock()
>>> lp = pickle.dumps(lock)
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    lp = pickle.dumps(lock)
...
RuntimeError: Lock objects should only be shared between processes through inheritance
>>> 

参见&#34; Picklability&#34;和#34;更好地继承而不是pickle / unpickle&#34; https://docs.python.org/2/library/multiprocessing.html#all-platforms

的各个部分

答案 2 :(得分:3)

其他答案已经提供了这样的答案:除非提供适当的apply_async参数,否则error_callback会以静默方式失败。我仍然发现OP的另一点有效-官方文档确实显示multiprocessing.Lock作为函数参数被传递。实际上,Programming guidelines中标题为“将资源显式传递给子进程”的小节建议将multiprocessing.Lock对象作为函数参数而不是全局变量。而且,我一直在编写许多代码,其中我将multiprocessing.Lock作为子进程的参数传递,并且所有代码均按预期工作。

那有什么作用?

我首先调查了multiprocessing.Lock是否可腌制。在Python 3(MacOS + CPython)中,尝试使multiprocessing.Lock腌制会产生其他人遇到的熟悉的RuntimeError

>>> pickle.dumps(multiprocessing.Lock())
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-7-66dfe1355652> in <module>
----> 1 pickle.dumps(multiprocessing.Lock())

/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/synchronize.py in __getstate__(self)
     99
    100     def __getstate__(self):
--> 101         context.assert_spawning(self)
    102         sl = self._semlock
    103         if sys.platform == 'win32':

/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/context.py in assert_spawning(obj)
    354         raise RuntimeError(
    355             '%s objects should only be shared between processes'
--> 356             ' through inheritance' % type(obj).__name__
    357             )

RuntimeError: Lock objects should only be shared between processes through inheritance

对我来说,这确认multiprocessing.Lock确实不是可腌制的。

从一边开始

但是,相同锁仍需要在两个或多个python进程之间共享,这些进程将具有自己的,可能不同的地址空间(例如,当我们使用“ spawn”或“ forkserver”启动方法)。 multiprocessing必须做一些特殊的事情才能跨进程发送Lock。该other StackOverflow post似乎表明,在Unix系统中,multiprocessing.Lock可以通过OS本身(Python外部)支持的命名信号量来实现。然后,两个或多个python进程可以链接到 same 锁,该锁实际上位于两个python进程之外的一个位置。可能还有共享内存的实现。

旁边结束

我们可以将multiprocessing.Lock对象作为参数传递吗?

经过更多的实验和更多的阅读后,似乎发现multiprocessing.Poolmultiprocessing.Process之间是有区别的。

multiprocessing.Process使您可以将multiprocessing.Lock作为参数传递,而multiprocessing.Pool则不能。这是一个可行的示例:

import multiprocessing
import time
from multiprocessing import Process, Lock


def task(n: int, lock):
    with lock:
        print(f'n={n}')
    time.sleep(0.25)


if __name__ == '__main__':
    multiprocessing.set_start_method('forkserver')
    lock = Lock()
    processes = [Process(target=task, args=(i, lock)) for i in range(20)]
    for process in processes:
        process.start()
    for process in processes:
        process.join()

请注意,如Programming guidelines的“安全导入主模块”小节中所述,必须使用__name__ == '__main__'

multiprocessing.Pool似乎使用了queue.SimpleQueue,这会将每个任务排入队列,这就是进行酸洗的地方。 multiprocessing.Process最有可能没有使用酸洗(或未进行酸洗的特殊版本)。