【问题标题】:Pass arguments through self in class instance while multiprocessing in Python在 Python 中进行多处理时,在类实例中通过 self 传递参数
【发布时间】:2020-03-23 00:11:40
【问题描述】:

似乎可行,但是在分叉后使用self 是否安全?还是应该始终通过args 将参数作为函数参数传递给子进程?

import multiprocessing as mp

class C():

    def __init__(self):
        self.v = 'bla'
        p = mp.Process(target=self.worker, args=[])
        #p = mp.Process(target=self.worker, args=(self.v,))
        p.start()
        p.join()

    def worker(self):
        print(self.v)

    #def worker(self, v):
        #print(v)

c = C()

# prints 'bla'

更具体地说,我想传递 manager.Queue() 对象,不确定是否有区别。

如果这是一个简单的 C fork(),因为整个过程复制相同 - 除了 pid -,self 将是相同的。但是 Python 多处理可能正在做一些我不知道的事情,或者在某处可能会出现警告,例如“不要这样使用它,这可能会在未来发生变化”。我没有找到任何专门解决这个问题的东西。

我真正担心的是args 中传递的参数,特别是如果它们与多处理模块相关联,可能会围绕 fork() 进行转换以避免任何问题。

Python 3.6.5

【问题讨论】:

    标签: python python-3.x class multiprocessing fork


    【解决方案1】:

    对于 fork 启动方法以外的任何方法,当调用 Process.start() 时,目标和参数都使用 pickling 发送到工作进程。对于fork方法,子进程在同一点被fork,所以当Process.start()被调用时。

    所以当你不使用fork start 方法时,你需要担心的是你的数据是否会被pickle。在这种情况下,没有理由避免使用类实例和self;整个实例被腌制,因为self.target 是一个包含对实例的引用的方法:

    >>> class C:
    ...     def __init__(self):
    ...         self.v = 'bla'
    ...     def worker(self):
    ...         print(self.v)
    ...
    >>> c = C()
    >>> data = pickle.dumps(c.worker)
    >>> pickletools.dis(data)
        0: \x80 PROTO      4
        2: \x95 FRAME      71
       11: \x8c SHORT_BINUNICODE 'builtins'
       21: \x94 MEMOIZE    (as 0)
       22: \x8c SHORT_BINUNICODE 'getattr'
       31: \x94 MEMOIZE    (as 1)
       32: \x93 STACK_GLOBAL
       33: \x94 MEMOIZE    (as 2)
       34: \x8c SHORT_BINUNICODE '__main__'
       44: \x94 MEMOIZE    (as 3)
       45: \x8c SHORT_BINUNICODE 'C'
       48: \x94 MEMOIZE    (as 4)
       49: \x93 STACK_GLOBAL
       50: \x94 MEMOIZE    (as 5)
       51: )    EMPTY_TUPLE
       52: \x81 NEWOBJ
       53: \x94 MEMOIZE    (as 6)
       54: }    EMPTY_DICT
       55: \x94 MEMOIZE    (as 7)
       56: \x8c SHORT_BINUNICODE 'v'
       59: \x94 MEMOIZE    (as 8)
       60: \x8c SHORT_BINUNICODE 'bla'
       65: \x94 MEMOIZE    (as 9)
       66: s    SETITEM
       67: b    BUILD
       68: \x8c SHORT_BINUNICODE 'worker'
       76: \x94 MEMOIZE    (as 10)
       77: \x86 TUPLE2
       78: \x94 MEMOIZE    (as 11)
       79: R    REDUCE
       80: \x94 MEMOIZE    (as 12)
       81: .    STOP
    highest protocol among opcodes = 4
    

    在上面的流中你可以清楚地看到v、'blah'和worker命名。

    如果您确实使用了 fork 启动方法,那么子进程就可以完全访问父进程内存中的所有内容; self 仍在引用您在分叉之前拥有的同一对象。您的操作系统会处理那里的细节,例如确保文件描述符是独立的,以及确保子进程获得正在更改的内存块的副本。

    无论哪种方式,您对实例所做的进一步更改对父进程都不可见,除非您明确使用data structures designed to be shared。

    【讨论】:

    • 只是我理解正确:target==self.worker 和 self.worker 有 self.worker.reference_to_self THUS 当target 被发送到新进程时,它将可以访问与以前相同的属性,这包括target.reference_to_self==self.worker.reference_to_self
    • @ZoltanK.:表达式self.worker返回的对象是一个绑定方法对象。该对象具有对实例的引用,这就是方法在调用时获取传递的实例的方式。你可以自己看这个,试试self.worker.__self__。为了将该方法发送到新进程,它会被腌制,并且腌制会记录有关在另一端重新创建它所需的方法对象的所有内容,包括实例属性值。所以是的,你理解正确。 :-)
    猜你喜欢
    • 1970-01-01
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    • 2017-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多