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
LeeReamond
V2EX  ›  Python

如何确保一个类是跨进程安全的?

  •  
  •   LeeReamond · 79 天前 · 2293 次点击
    这是一个创建于 79 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求,一个异步脚本里大量使用run_in_executor()封装同步调用,然后脚本本身用了 os.fork()搞出很多子进程。理论上最好所有子进程共享同一个线程池而不是每个进程单独拥有一个线程池。

    import asyncio, time, os 
    from concurrent.futures import ThreadPoolExecutor
    
    executor = ThreadPoolExecutor()
    
    def sync_call(x):
        time.sleep(0.1)
        return x+1
    
    async def main():
        loop = asyncio.get_running_loop()
        while True:
            res = await asyncio.gather(*(loop.run_in_executor(executor, sync_call, x) for x in range(100)))
            assert res == [_ for _ in range(1,101)]
            print(True)
            
    if __name__ == '__main__':
        for _ in range(16):
            pid = os.fork()
            if pid == 0:
                break
        asyncio.run(main())
    

    简化下来大概是这样的代码,其中线程池是自己几年前写的,当时确保了在单进程内使用多线程的情况下是绝对安全的,现在已经看不懂代码了,怎样才能知道多进程的情况下是否能确保安全呢?

    上文这个代码拿来跑当测试,倒是也没报错,但是我怎么知道它是不是刚好没遇到特殊情况所以没坑。。

    12 条回复    2022-04-19 20:24:22 +08:00
    janus77
        1
    janus77  
       79 天前
    太经典了,自己写的代码过一段时间就看不懂了
    gfreezy
        2
    gfreezy  
       79 天前
    进程之间不共享(好像除了 fd 以外的)任何东西,包括线程。
    fcfangcc
        3
    fcfangcc  
       79 天前
    进程之间应该是无法共享线程池。为什么要 fork 多个子进程,直接启动多个 task 不就好了吗
    fcfangcc
        4
    fcfangcc  
       79 天前
    asyncio.wait([main() for i in range(16)])
    qbqbqbqb
        5
    qbqbqbqb  
       79 天前
    用多进程的话,传递给子进程 target 函数的参数和传回来的返回值必须是能用 pickle 序列化、反序列化的,或者是 multiprocessing 库里面的 Queue, Value, Array 之类的专用的进程间通信工具。不能随便传递其它对象。

    另外就是子进程也不能随便用全局变量、全局对象(常量可以用,有状态的对象慎用),因为子进程里全局对象的初始状态是有区别的,而且修改之后也不会同步到其它进程。

    Python 多进程有三种模式 fork, spawn, forkserver:
    1 ) fork 模式子进程会继承此时主进程的状态(相当于当前 Python 分裂成两个,其中主进程继续执行当前函数,子进程跳到 target 函数)
    2 ) spawn 和 forkserver 模式子进程为程序刚初始化后的状态(相当于重新启动了一个 Python import 了所有库,然后不执行__main__直接跳到 target 函数开始执行)。

    Linux 默认 fork ,Mac py3.8 以后默认 spawn, Windows 只支持 spawn 。
    lolizeppelin
        6
    lolizeppelin  
       79 天前   ❤️ 1
    你这需求老老实实写线程. 协程都别写

    觉得 python 线程不行要么换语言要么自己写 c 库

    什么跨进程共享线程池都出来了
    qbqbqbqb
        7
    qbqbqbqb  
       79 天前
    @qbqbqbqb #5 是针对 Multiprocessing 多进程的。os.fork()就简单粗暴很多,子进程继承 fork 时的状态,之后对象状态的改变就不共享了,所以后来创建的线程池肯定是每个进程一个。想要统一池子还不如直接用 Multiprocessing 里的进程池。
    cyrbuzz
        8
    cyrbuzz  
       79 天前
    多进程共享同一个线程即使可以的话优势在什么地方?这是为了解决什么问题= =。
    lyz1990
        9
    lyz1990  
       79 天前
    进程能共享线程池么?
    xuanbg
        10
    xuanbg  
       79 天前
    多实例化几个对象不行么。。。非要跨什么进程。
    lolizeppelin
        11
    lolizeppelin  
       79 天前
    @qbqbqbqb
    Multiprocessing 就是个大坑,读下 Multiprocessing 代码就知道了

    Multiprocessing 就适合跑下简单业务,稍微复杂点的拿 Multiprocessing 跑简直找死
    LeeReamond
        12
    LeeReamond  
    OP
       79 天前
    @qbqbqbqb
    @fcfangcc
    @janus77
    @gfreezy
    @lolizeppelin
    @xuanbg 异步是为了让过程调用受网络管理,需求是 CPU 密集型任务所以使用 fork 多进程,楼上老哥说得对,我试了一下好像真的除了 fd 以外不共享任何数据,跟我记忆中有些偏差(我印象中不特意创建进程间可共享内存也有同指向发生,试了下好像除了虚拟内存表以外物理内存表也全拷贝了,完全的互不相干。。)


    关于代码写完过几年看不懂的问题,因为是开源项目其实当时还写了蛮详细的注释的,只不过是用英文写的,现在看注释一大坨一大坨像看论文一样实在不想看。可能这个故事教育我们就是不要好面子写英文,外国人看不看得懂不是最重要的,自己能看懂才是。。
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2908 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 14:17 · PVG 22:17 · LAX 07:17 · JFK 10:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.