Python 在多处理应用异步中维护实例状态
我希望,如果我在一个实例方法中调用Python 在多处理应用异步中维护实例状态,python,oop,python-multiprocessing,Python,Oop,Python Multiprocessing,我希望,如果我在一个实例方法中调用apply\u async,并得到它的结果,那么所做的任何更改都将成为分叉过程的一部分。然而,似乎每个应用异步的新调用都会创建所述实例的新副本 以下面的代码为例: 来自multiprocessing.pool导入池的 类别多重测试: 定义初始化(自): self.i=0 def运行(自): 将池(2)作为池: 工人职位=[] 对于范围(10)内的j: job=pool.apply\u async(self.process,(j,)) worker\u jobs.
apply\u async
,并得到它的结果,那么所做的任何更改都将成为分叉过程的一部分。然而,似乎每个应用异步的新调用都会创建所述实例的新副本
以下面的代码为例:
来自multiprocessing.pool导入池的
类别多重测试:
定义初始化(自):
self.i=0
def运行(自):
将池(2)作为池:
工人职位=[]
对于范围(10)内的j:
job=pool.apply\u async(self.process,(j,))
worker\u jobs.append(作业)
对于worker_作业中的作业:
res=job.get()
打印(“输入”,分辨率)
def流程(自我、inp):
打印(“i”,self.i)
self.i+=1
返回inp
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
mt=多重测试()
润山(
样本输出:
i 0
i 0
i 0
i 0
i 0
input 0
i 0
i 0
i 0
i 0
i 0
input 1
input 2
input 3
input 4
input 5
input 6
input 7
input 8
input 9
但由于我们有两个内核,其中10个输入分布在这两个内核上,所以我希望I
属性会增加
我原以为会出现以下情况:
- 主线程创建实例并调用
run()
- 主线程通过初始化两个新进程和原始Multitest实例(其中
)的副本,在池上分发i=0
的工作apply\u async
- 对新进程多次调用
(直到process()
耗尽)。每次调用process时,该进程的self.i都会递增range()
self.I
没有增加)
然而,我没有看到这种行为。相反,打印的输出仅为零,这表明我的期望是错误的:状态(属性i
)没有维护,但每次调用apply\u async
时都会创建一个新实例(或至少一个新副本)。我在这里遗漏了什么,我如何才能使这项工作如预期的那样成功?(最好使用apply\u async
,尽管不是必需的。但应保持结果的顺序。)
据我所知,这种行为并不特定于
apply\u async
,也适用于其他pool
方法。我有兴趣了解为什么会发生这种情况,以及如何将行为改变为我想要实现的行为。Bounty找到了可以为这两个问题提供答案的答案。我想向您指出参考资料,但我还没有任何参考资料,因此我将根据经验证据分享我的想法:
每次调用apply_async都会准备名称空间的新副本。您可以通过在流程内部添加对print(self)
的调用来看到这一点。所以这部分不是真的:
主线程分配工作。。。通过初始化两个新进程和
原始Multitest实例的副本
相反,有两个新进程和原始Multitest实例的十个副本。所有这些副本都是从主进程生成的,主进程的i副本没有增加。为了证明这一点,添加time.sleep(1);self.i+=1
在调用apply\u async之前,请注意a)主线程中i的值递增,b)通过延迟for循环,在下一次调用apply\u async触发新副本时,原始多测试实例已更改
代码:
结果:
Creating new Multitest instance: <__main__.Multitest object at 0x1056fc8b0>
i 1
Copied instance: <__mp_main__.Multitest object at 0x101052d90>
i 2
Copied instance: <__mp_main__.Multitest object at 0x101052df0>
i 3
Copied instance: <__mp_main__.Multitest object at 0x101052d90>
input 0
input 1
input 2
i 4
Copied instance: <__mp_main__.Multitest object at 0x101052df0>
input 3
Creating new Multitest instance: <__main__.Multitest object at 0x1083f3880>
i 0 (pid: 3460)
Copied instance: <__mp_main__.Multitest object at 0x10d89cdf0>
i 0 (pid: 3463)
Copied instance: <__mp_main__.Multitest object at 0x10d89ce50>
Copied instance: <__mp_main__.Multitest object at 0x10550adf0>
i 0 (pid: 3462)
Copied instance: <__mp_main__.Multitest object at 0x10550ae50>
i 1 (pid: 3462)
i 1 (pid: 3463)
input 0
input 1
input 2
input 3
结果:
Creating new Multitest instance: <__main__.Multitest object at 0x1056fc8b0>
i 1
Copied instance: <__mp_main__.Multitest object at 0x101052d90>
i 2
Copied instance: <__mp_main__.Multitest object at 0x101052df0>
i 3
Copied instance: <__mp_main__.Multitest object at 0x101052d90>
input 0
input 1
input 2
i 4
Copied instance: <__mp_main__.Multitest object at 0x101052df0>
input 3
Creating new Multitest instance: <__main__.Multitest object at 0x1083f3880>
i 0 (pid: 3460)
Copied instance: <__mp_main__.Multitest object at 0x10d89cdf0>
i 0 (pid: 3463)
Copied instance: <__mp_main__.Multitest object at 0x10d89ce50>
Copied instance: <__mp_main__.Multitest object at 0x10550adf0>
i 0 (pid: 3462)
Copied instance: <__mp_main__.Multitest object at 0x10550ae50>
i 1 (pid: 3462)
i 1 (pid: 3463)
input 0
input 1
input 2
input 3
创建新的多测试实例:
I0(pid:3460)
复制的实例:
I0(pid:3463)
复制的实例:
复制的实例:
I0(pid:3462)
复制的实例:
i 1(pid:3462)
i 1(pid:3463)
输入0
输入1
输入2
投入3
此技术来自于多处理和线程之间的一个区别是,在创建进程后,它使用的内存实际上是从其父进程克隆而来的,因此进程之间没有共享内存 以下是一个例子:
导入操作系统
导入时间
从线程导入线程
全局计数器=0
定义我的线程()
全局计数器
打印(“在线程中,全局\u计数器为%r,添加一个。”%global\u计数器)
全局计数器+=1
def测试_线程():
全局计数器
th=线程(目标=我的线程)
th.start()
加入
打印(“在父线程、子线程连接中,全局\u计数器现在是%r。”%global\u计数器)
def test_fork():
全局计数器
pid=os.fork()
如果pid==0:
打印(“在子进程中,全局\u计数器为%r,添加一个。”%global\u计数器)
全局计数器+=1
退出()
时间。睡眠(1)
打印(“在父进程中,子进程已死亡,全局\u计数器仍然是%r.%global\u计数器)
def main():
测试_线程()
测试叉()
如果名称=“\uuuuu main\uuuuuuuu”:
main()
输出:
in thread, global_counter is 0, add one.
in parent, child thread joined, global_counter is 1 now.
in child process, global_counter is 1, add one.
in parent, child process died, global_counter is still 1.
original instance is <__main__.Multitest object at 0x1072828d0>
pickle duplicates the instance, new instance is <__main__.Multitest object at 0x107283110>
就你而言:
for j in range(10):
# Before fork, self.i is 0, fork() dups memory, so the variable is not shared to the child.
job = pool.apply_async(self.process, (j,))
# After job finishes, child's self.i is 1 (not parent's), this variable is freed after child dies.
worker_jobs.append(job)
编辑:
在python3中,绑定方法也会包括对象本身,本质上是复制它。因此,每次调用apply\u async
时,对象self
也会被pickle
import os
from multiprocessing.pool import Pool
import pickle
class Multitest:
def __init__(self):
self.i = "myattr"
def run(self):
with Pool(2) as pool:
worker_jobs = []
for j in range(10):
job = pool.apply_async(self.process, (j,))
worker_jobs.append(job)
for job in worker_jobs:
res = job.get()
print("input", res)
def process(self, inp):
print("i", self.i)
self.i += "|append"
return inp
def test_pickle():
m = Multitest()
print("original instance is %r" % m)
pickled_method = pickle.dumps(m.process)
assert b"myattr" in pickled_method
unpickled_method = pickle.loads(pickled_method)
# get instance from it's method (python 3)
print("pickle duplicates the instance, new instance is %r" % unpickled_method.__self__)
if __name__ == '__main__':
test_pickle()
输出:
in thread, global_counter is 0, add one.
in parent, child thread joined, global_counter is 1 now.
in child process, global_counter is 1, add one.
in parent, child process died, global_counter is still 1.
original instance is <__main__.Multitest object at 0x1072828d0>
pickle duplicates the instance, new instance is <__main__.Multitest object at 0x107283110>
原始实例是
pickle复制实例,新实例为
我相信以下情况正在发生:
self.process
,方法都会被序列化(pickle)并发送到子进程。每次都会创建一个新副本Multitest
实例,因为只有当\uuuuuu name\uuuuuu=='\uuuuuu main\uuuuu'
不适用于池创建的分叉时,才会创建该实例
如果要在child pro中保持状态