from multiprocessing import Process
import time, random
class Example:
pass
def fun(i, case):
time.sleep(random.randint(1, 4))
print("{}, age is {}".format(i, case.age))
def main():
for i in range(3):
setattr(Example, "age", i)
print("satrt", Example.age)
ps = Process(target=fun, args=(i, Example))
ps.start()
if __name__ == '__main__':
main()
代码如上所示, 但是最终在 fun 函数里面报了 AttributeError: type object 'Example' has no attribute 'age' 这个错误,setattr 没有生效, 请问一下大佬原因。
1
minami 2021-11-16 22:07:16 +08:00
e = Example()
setattr(e, "age", i) print("satrt", e.age) ps = Process(target=fun, args=(i, e)) ps.start() 传一个实例进去 |
2
Nitroethane 2021-11-16 22:50:20 +08:00
|
3
Nitroethane 2021-11-16 23:07:37 +08:00
@Nitroethane #2 请忽略这个回答,纯粹胡扯…… (被 bug 搞了一天,大脑处于混乱状态)。出现这个现象的原因是不同版本的 multiprocessing 库使用的默认的 start_method 导致的。3.9.7 上的 start_method 是 spawn ,而 3.7.5 是 fork ,所以 3.7.5 下面每次打印的 id 值是相同的。
|
4
18870715400 OP @Nitroethane 谢谢, 我看一下 fork 和 spawn 的区别.
|
5
18870715400 OP @minami 额, 就是因为不想传实例进去.
|
6
ClericPy 2021-11-17 00:13:30 +08:00
题外话:
最近踩坑挺多的, 并行计算还是尽量无状态, 纯函数越纯越好... |
7
Nitroethane 2021-11-17 00:19:40 +08:00
@18870715400 #4 fork 模式的工作机制应该和 fork 系统调用类似,子进程和父进程的地址空间完全一致,因此引用的是同一个对象。
spawn 模式的工作机制应该和 execve 系统调用类似,用 fork 系统调用产生子进程后会用 execve 系统调用加载一个全新的 python 解释器实例,这时子进程和父进程的地址空间就不同了。不过这还不能解释,为什么给子进程传一个实例化的对象就没问题,我猜应该和内部的具体实现有关。 如果想快速解决这个错误的话,只需要在 main 函数最开始掉用一下 multiprocessing.set_start_method() 方法设置成 fork 。 |
8
Nitroethane 2021-11-17 00:21:56 +08:00
@ClericPy #6 同感,在 python 里搞多线程 /多进程纯粹是自讨苦吃。最近接手一个老项目,多进程套多线程,而且还是 python2 的,给我搞吐了。幸亏项目不大,迁移到 3.9 加部分重构花了三天时间
|
9
ClericPy 2021-11-17 00:31:49 +08:00
@Nitroethane
上一份工作维护老项目 Python2 是真挺头疼的 不过我现在上头下头都没别人, 所以选型时候我 all-in 协程了, 感觉算太香但还好. 随手写个流式下载上传, 把多进程和协程用上以后 CPU 利用率长时间 400%, 内存只有旧的 Java 版本的十分之一(似乎主要是流式传输占便宜...), 开发时间也确实才两三天. 就是交接工作时候不知道咋办了, 招不到玩协程的, 最多就是几个用过 gevent 猴子补丁, 头疼 现在想整轮子或者整个 Snippet 想办法利用多核时候少写几行代码, py 爹似乎也在关注这方向了, 子解释器还不知道有没有坑. 总之多进程时候我真是能不传变量就不传变量了, 貌似现在更建议通过通信来共享内存不去通过共享内存来通信, 多进程那些 Manager Queue Value Lock 什么的还是担心有坑 |
10
Nitroethane 2021-11-17 00:42:26 +08:00 via iPhone
@ClericPy 我主要是很长时间没写过 Python 了,一直写的 go ,而且以前写 Python 的时候学了一阵协程愣是没搞懂,然后就扔下了。直到最近翻了下流畅的 Python 里关于协程的部分才通透了。
我用多进程的时候就是 Manager 那一套,先创建 Manager ,然后用 Manager 创建 Queue ,通过这个 Queue 在进程和线程之间传递数据。等后面有时间再 all-in 协程了 |
11
dongyx 2021-11-17 00:52:16 +08:00 4
multiprocessing 模块有一个叫做 start_method 的全局模块属性。如果设置为 spawn ,那么启动的子进程会重置为一个干净的 Python 解释器并重新定位到要执行的函数,相当于于执行 fork+execv 系统调用然后执行你的函数。如果设置为 fork ,子进程会写时拷贝父进程的虚拟地址空间,类似于执行 fork 系统调用之后直接执行函数。
当 start_method 被设置为 spawn 的时候,Process 对象的 args 参数是通过 pickle 模块序列化之后跨进程传递给子进程的。而 pickle 在序列化类和函数的时候,仅仅是保存他们的完整名字引用。所以你可以认为你的 Example 类被序列化为一个类似于 b'__main__.Example'的字节序列。子进程通过这个名字定为 Example 类,而子进程是一个干净的解释器,所以 Example 类也是干净的。 在 UNIX 上,Python 默认使用 fork 模式,而在 Windows 上默认使用(也只支持) spawn 模式。但是有一点要特别注意,尽管 macOS 是一个 UNIX ,但是从 Python 3.8 开始,也以 spawn 作为默认的 start_method (但保持对 for 的支持)。 你可以选择在程序引入 multiprocess 模块后,通过执行`multiprocessing.set_start_method('fork')`来解决你的问题。 |
12
18870715400 OP @dongyx 好的, 谢谢大佬
|
13
haoliang 2021-11-17 14:54:09 +08:00
python 3.9.7, archlinux ; 运行正常
|
14
joApioVVx4M4X6Rf 2021-11-18 09:41:55 +08:00
闻到了坑的味道
|