跨进程在队列中传递对象引用

时间:2019-06-19 19:09:57

标签: python python-multiprocessing

我有几个multiprocessing.Process,并且希望它们消耗(队列get())可调用的非可拾取对象并对其进行调用。它们是在fork()之前创建的,因此不需要酸洗。

使用multiprocessing.Queue无效,因为它会尝试腌制所有内容:

import multiprocessing as mp

# create non-global callable to make it unpicklable
def make_callable():
    def foo():
        print("running foo")
    return foo

def bar():
    print("running bar")

def runall(q):
    while True:
        c = q.get()
        if c is None:
            break
        c()

if __name__ == '__main__':
    q = mp.Queue()
    call = make_callable()
    p = mp.Process(target=runall, args=(q,))
    p.start()
    q.put(bar)
    q.put(call)
    q.put(None)
    p.join()
running bar
Traceback (most recent call last):
  File "/usr/lib64/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/usr/lib64/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
AttributeError: Can't pickle local object 'make_callable.<locals>.foo'

等效的实现方式是将所有对象放入全局(或传递的)列表中,并仅传递索引,这将起作用:

import multiprocessing as mp

# create non-global callable to make it unpicklable
def make_callable():
    def foo():
        print("running foo")
    return foo

def bar():
    print("running bar")

def runall(q, everything):
    while True:
        c = q.get()
        if c is None:
            break
        everything[c]()

if __name__ == '__main__':
    q = mp.Queue()
    call = make_callable()
    everything = [bar, call]
    p = mp.Process(target=runall, args=(q,everything))
    p.start()
    q.put(0)
    q.put(1)
    q.put(None)
    p.join()
running bar
running foo

问题是,尽管我知道传递的所有可调用项都不会被垃圾回收(因此它们的地址将保持有效),但我没有完整的列表。

我也知道我可能可以使用multiprocessing.Manager对象使用Queue及其Proxy实现,但这似乎有很多开销,尤其是在实际实现中也传递其他可腌制数据。

是否有一种方法可以腌制并将地址引用仅传递给在多个进程之间共享的对象?

谢谢!

2 个答案:

答案 0 :(得分:0)

确定Process的目标对象必须是可拾取的。

  

请注意,功能(内置和用户定义)由“完全   合格”名称参考,而不是按值。这意味着仅   腌制函数名称以及模块名称   函数是在其中定义的。函数的代码或其任何形式均未定义   函数属性被腌制。因此,定义模块必须是   可在解酸环境中导入,并且模块必须包含   命名对象,否则将引发异常。

可拾取的函数和类必须在模块的顶层定义。

因此,在您的情况下,您需要继续传递顶级可调用对象,但在关键的runall函数中应用其他检查/解决方法:

import multiprocessing as mp

# create non-global callable to make it unpicklable
def make_callable():
    def foo():
        print("running foo")
    return foo

def bar():
    print("running bar")

def runall(q):
    while True:
        c = q.get()
        if c is None:
            break

        res = c()
        if callable(res): res()


if __name__ == '__main__':
    q = mp.Queue()
    p = mp.Process(target=runall, args=(q,))
    p.start()

    q.put(bar)
    q.put(make_callable)
    q.put(None)

    p.join()
    q.close() 

输出:

running bar
running foo

答案 1 :(得分:0)

经过一番思考和搜索,我相信我已经在寻找答案,主要是从Get object by id()?中获得。

我可以传递id()的可调用对象,然后在产生的过程中将其转换回去:

import ctypes
a = "hello world"
print ctypes.cast(id(a), ctypes.py_object).value

或者使用gc模块,并且只要我保持对对象的引用仍然有效,就可以使用

import gc

def objects_by_id(id_):
    for obj in gc.get_objects():
        if id(obj) == id_:
            return obj
    raise Exception("No found")

但是,这两种方法都不是很干净,最后,值得限制一下所有可调用对象必须首先传递索引。