V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
18870715400
V2EX  ›  Python

多进程传递进去的值没有生效。

  •  
  •   18870715400 · 2021-11-16 21:59:01 +08:00 · 2072 次点击
    这是一个创建于 863 天前的主题,其中的信息可能已经有所发展或是发生改变。
    
    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 没有生效, 请问一下大佬原因。

    14 条回复    2021-11-18 09:41:55 +08:00
    minami
        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()

    传一个实例进去
    Nitroethane
        2
    Nitroethane  
       2021-11-16 22:50:20 +08:00
    `Example` 的类型是 `<class 'type'>`。刚试了下,3.9 对于 type 类型是传值,3.7.5 是传引用。因此你这个代码在 3.7.5 上是能正常跑的,但是在 3.9.6 上会报 AttritubeError 。
    把代码改成这样,运行结果在两个版本上是不同的:

    Nitroethane
        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 值是相同的。
    18870715400
        4
    18870715400  
    OP
       2021-11-16 23:51:29 +08:00
    @Nitroethane 谢谢, 我看一下 fork 和 spawn 的区别.
    18870715400
        5
    18870715400  
    OP
       2021-11-16 23:52:01 +08:00
    @minami 额, 就是因为不想传实例进去.
    ClericPy
        6
    ClericPy  
       2021-11-17 00:13:30 +08:00
    题外话:

    最近踩坑挺多的, 并行计算还是尽量无状态, 纯函数越纯越好...
    Nitroethane
        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 。
    Nitroethane
        8
    Nitroethane  
       2021-11-17 00:21:56 +08:00
    @ClericPy #6 同感,在 python 里搞多线程 /多进程纯粹是自讨苦吃。最近接手一个老项目,多进程套多线程,而且还是 python2 的,给我搞吐了。幸亏项目不大,迁移到 3.9 加部分重构花了三天时间
    ClericPy
        9
    ClericPy  
       2021-11-17 00:31:49 +08:00
    @Nitroethane

    上一份工作维护老项目 Python2 是真挺头疼的

    不过我现在上头下头都没别人, 所以选型时候我 all-in 协程了, 感觉算太香但还好. 随手写个流式下载上传, 把多进程和协程用上以后 CPU 利用率长时间 400%, 内存只有旧的 Java 版本的十分之一(似乎主要是流式传输占便宜...), 开发时间也确实才两三天. 就是交接工作时候不知道咋办了, 招不到玩协程的, 最多就是几个用过 gevent 猴子补丁, 头疼

    现在想整轮子或者整个 Snippet 想办法利用多核时候少写几行代码, py 爹似乎也在关注这方向了, 子解释器还不知道有没有坑.

    总之多进程时候我真是能不传变量就不传变量了, 貌似现在更建议通过通信来共享内存不去通过共享内存来通信, 多进程那些 Manager Queue Value Lock 什么的还是担心有坑
    Nitroethane
        10
    Nitroethane  
       2021-11-17 00:42:26 +08:00 via iPhone
    @ClericPy 我主要是很长时间没写过 Python 了,一直写的 go ,而且以前写 Python 的时候学了一阵协程愣是没搞懂,然后就扔下了。直到最近翻了下流畅的 Python 里关于协程的部分才通透了。
    我用多进程的时候就是 Manager 那一套,先创建 Manager ,然后用 Manager 创建 Queue ,通过这个 Queue 在进程和线程之间传递数据。等后面有时间再 all-in 协程了
    dongyx
        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')`来解决你的问题。
    18870715400
        12
    18870715400  
    OP
       2021-11-17 09:22:38 +08:00
    @dongyx 好的, 谢谢大佬
    haoliang
        13
    haoliang  
       2021-11-17 14:54:09 +08:00
    python 3.9.7, archlinux ; 运行正常
    v2exblog
        14
    v2exblog  
       2021-11-18 09:41:55 +08:00
    闻到了坑的味道
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1112 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 22:49 · PVG 06:49 · LAX 15:49 · JFK 18:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.