Python 在进程之间共享锁

Posted

技术标签:

【中文标题】Python 在进程之间共享锁【英文标题】:Python sharing a lock between processes 【发布时间】:2014-10-22 20:10:06 【问题描述】:

我正在尝试使用部分函数,​​以便 pool.map() 可以针对具有多个参数的函数(在本例中为 Lock() 对象)。

这里是示例代码(取自我上一个问题的答案):

from functools import partial

def target(lock, iterable_item):
    for item in items:
        # Do cool stuff
        if (... some condition here ...):
            lock.acquire()
            # Write to stdout or logfile, etc.
            lock.release()

def main():
    iterable = [1, 2, 3, 4, 5]
    pool = multiprocessing.Pool()
    l = multiprocessing.Lock()
    func = partial(target, l)
    pool.map(func, iterable)
    pool.close()
    pool.join()

但是当我运行这段代码时,我得到了错误:

Runtime Error: Lock objects should only be shared between processes through inheritance.

我在这里缺少什么?如何在我的子进程之间共享锁?

【问题讨论】:

关于同一个问题还有另一个问题,尽管他们的具体错误不同 - Trouble using a lock with multiprocessing.Pool: pickling error 【参考方案1】:

您不能将普通的 multiprocessing.Lock 对象传递给 Pool 方法,因为它们不能被腌制。有两种方法可以解决这个问题。一种是创建Manager()并传递Manager.Lock()

def main():
    iterable = [1, 2, 3, 4, 5]
    pool = multiprocessing.Pool()
    m = multiprocessing.Manager()
    l = m.Lock()
    func = partial(target, l)
    pool.map(func, iterable)
    pool.close()
    pool.join()

不过,这有点重量级;使用Manager 需要生成另一个进程来托管Manager 服务器。并且所有对acquire/release 的调用都必须通过 IPC 将锁发送到该服务器。

另一个选项是在创建池时传递常规的multiprocessing.Lock(),使用initializer kwarg。这将使您的锁实例在所有子工作者中都是全局的:

def target(iterable_item):
    for item in items:
        # Do cool stuff
        if (... some condition here ...):
            lock.acquire()
            # Write to stdout or logfile, etc.
            lock.release()
def init(l):
    global lock
    lock = l

def main():
    iterable = [1, 2, 3, 4, 5]
    l = multiprocessing.Lock()
    pool = multiprocessing.Pool(initializer=init, initargs=(l,))
    pool.map(target, iterable)
    pool.close()
    pool.join()

第二种解决方案的副作用是不再需要partial

【讨论】:

@dano 非常感谢您的回答,我也有相同的查询,这个查询完美地解决了它,但是我有另一个查询,为什么不经常使用这种方法在进程之间共享状态而不是通过一个 Manager 对象这样做,它有自己的运行服务器进程和代理访问的开销? @bawejakunal multiprocessing.Lock 是一个进程安全的对象,因此您可以将它直接传递给子进程并在所有子进程中安全地使用它。但是,大多数可变 Python 对象(如 listdict、大多数用户创建的类)都不是进程安全的,因此在进程之间传递它们会导致创建的对象的完全不同的副本每个过程。在这些情况下,您需要使用multiprocessing.Manager 你能解释一下为什么使用Pool时Lock需要作为全局变量创建,而使用Process时可以作为参数传递吗? @neilxdims 使用Process,Lock 在分叉时由子进程继承,并直接传递给您作为进程目标传递的任何方法。使用Pool,当您将参数传递给mapapply 等方法时,进程已经分叉,因此继承将不起作用。然而Poolinitializer函数的参数是继承的,所以Lock可以被传递。但是,对于池进程执行以访问Lock 的方法,它需要是全局的;否则将无法在initializer 函数之外访问。 @pistacchio 在 Linux 平台上,您可以这样做。在 Windows 上,您不能,因为 Windows 不支持分叉。你最终会在每个子进程中得到一个不同的锁对象。【参考方案2】:

这是一个版本(使用Barrier 而不是Lock,但你明白了)也可以在Windows 上运行(缺少fork 会导致额外的麻烦):

import multiprocessing as mp

def procs(uid_barrier):
    uid, barrier = uid_barrier
    print(uid, 'waiting')
    barrier.wait()
    print(uid, 'past barrier')    

def main():
    N_PROCS = 10
    with mp.Manager() as man:
        barrier = man.Barrier(N_PROCS)
        with mp.Pool(N_PROCS) as p:
            p.map(procs, ((uid, barrier) for uid in range(N_PROCS)))

if __name__ == '__main__':
    mp.freeze_support()
    main()

【讨论】:

以上是关于Python 在进程之间共享锁的主要内容,如果未能解决你的问题,请参考以下文章

python-- 多进程的基本语法 进程间数据交互与共享进程锁和进程池的使用

如何在工作在同一共享内存区域的两个进程之间共享锁?

锁对象只能通过继承在进程之间共享

Python 3 并发编程多进程之进程同步(锁)

互斥锁,IPC队列

37. Python 多进程锁 多进程共享内存