• python 多进程和异步io的有机结合 Error in atexit._run_exitfuncs


      众所周知,python的多线程开发在GIL(全局器解释锁)下饱受诟病,在单核模式下搞多线程对效率的提升相当有限。于是大家的共识就是搞io密集的程序,建议采用多线程,计算密集型的程序就搞多进程。近期的一些开发经历,让我大量尝试采用多进程和异步io的方式来提高效率。
      一.采用多进程。
      1.用过multiprocessing.process和queue及pool,但是一直有报错,位置在multiprocessing.spawn的_main(fd,parent_sentinel)中reduction.pickle.load(from_parent)。网上提供了各种诸如修改权限,修改reduction协议类型的方案,始终未能解决。最后竟然是更新了python版本后不药而愈,但最后放弃了这种方式。
      2.使用concurrent.futures.ProcessPoolExecutor,官方文档很详细。程序运行也很不错,但是在脚本执行完毕后退出时抛出异常。Error in atexit._run_exitfuncs。大致的内容是handle is closed,通过追踪大致可以判断出脚本执行完毕时,会有futures.process的_python_exit()执行,此时ProcessPoolExecutor执行完毕后释放,没有线程可以被wakeup,所以报错。引起这个问题的是官方推荐的with as写法,with as 执行完毕后会将对象释放,结果在退出的时候引发异常。
      解决方案也很反常,不使用with as的自动释放,也不使用shutdown手动释放,而是不释放,在整个脚本执行完毕的时候,由_python_exit()进行释放。
      有意思的是concurrent.futures.process文档里有这么一段注释,可以自行研究。
    # Workers are created as daemon threads and processes. This is done to allow the
    # interpreter to exit when there are still idle processes in a
    # ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However,
    # allowing workers to die with the interpreter has two undesirable properties:
    # - The workers would still be running during interpreter shutdown,
    # meaning that they would fail in unpredictable ways.
    # - The workers could be killed while evaluating a work item, which could
    # be bad if the callable being evaluated has external side-effects e.g.
    # writing to a file.
    #
    # To work around this problem, an exit handler is installed which tells the
    # workers to exit when their work queues are empty and then waits until the
    # threads/processes finish.
      二.采用异步io。
      python在3.4中有了asyncio。官方给的示例可以看出来这是个消息循环,eventloop是在当前的上下文中提供的,而且可以和协程一起执行。最新的文档里可以看到eventloop已经不再只是同协程一起提供异步了。
      我们可以将多个耗时的任务进行封装,丢进eventloop中,当线程执行到需要等待的操作如io,主线程不会等待,而是切换执行到下一个可执行任务,因此可以实现并发执行。
      三.多进程和异步io结合。
      经过尝试多线程中没有找到使用异步io的方式,因为eventloop是从当前上下文中提供的,也就是主线程。于是换了个思路,使用多进程,让每个进程的‘主线程’通过异步io来实现并发,经多次尝试后这种方案是可行的。
      因为同时运行的进程数量上限是受cpu内核数量的上限影响的,一般的建议是不超过内核数量,但是在一些场景的限制下为了提高效率,单纯受限的多进程不能满足要求的时候,不妨将多进程的‘主进程’结合并发来提高效率。
      由于没有实际测试性能,不能断言究竟哪种方式效率更高,毕竟也与任务内容有关。这只是一种思路,留待日后验证。
      代码如下:
    import asyncio
    import aiohttp
    from concurrent.futures import ProcessPoolExecutor, as_completed ,ThreadPoolExecutor
    import time
    
    
    async def post_http():
        # 示例
        url = ''
        data = ''
        async  with aiohttp.ClientSession() as session:
            async with session.post(url=url, data=data, headers={}, timeout=60) as resp:
                r_json = await resp.json()
                return r_json
    
    
    async def t_handler(data, t_flag, p_flag, semaphore):
        async with semaphore:
            for d in data:
                print(f'pid:{p_flag} tid:{t_flag} data:{d}')
                await asyncio.sleep(1)  # 处理费时的io操作,比如httprequest
        return
    
    
    def p_handler(datas, p_flag):
        # 线程并发数需要有限制  linux打开文件最大默认为1024 win为509 待确认
        ts = time.time()
        num = 10  # 最大并发数
        count = len(datas)
        block = int(count / num) + 1
        tar_datas = [datas[i * block: (i + 1) * block if (i + 1) * block < count else count] for i in range(num)]
        semaphore = asyncio.Semaphore(num)
        tasks = [t_handler(d, i, p_flag, semaphore) for i, d in enumerate(tar_datas)]
    
        loop = asyncio.get_event_loop()  # 基于当前线程 ,故在多线程中无法使用 只能在多进程中使用
        loop.run_until_complete(asyncio.wait(tasks))
        loop.close()
    
        return f'33[0;32mprocess {p_flag} :cost {time.time() - ts}33[0m'
    
    
    
    
    if __name__ == '__main__':
        ts = time.time()
        datas = [i for i in range(1000)]
        datas = [datas[i * 100:(i + 1) * 100] for i in range(10)]  # 每个进程要处理的数据
    
        # 启动异步io 主线程调用 event_loop 在当前线程下启动异步io 实现并发
        # res = p_handler(datas,1)
        # print(res)
    
        p_num = 10
        block_len = 100
    
        datas = [datas[i * 100:(i + 1) * 100] for i in range(p_num)]  # 每个进程要处理的数据
        # ProcessPoolExecutor 可能与运行环境有关 官方的 with as 会主动释放线程 导致主线程退出时找不到进程池内进程已经被释放 导致Error in atexit._run_exitfuncs异常
        executor = ProcessPoolExecutor(p_num)
        futures = [executor.submit(p_handler, d, p_flag) for p_flag, d in enumerate(datas)]
        for f in as_completed(futures):
            if f.done():
                    res = f.result()
                    print(res)
    
        print(f'Exit!! cost:{time.time() - ts}')
    

      

  • 相关阅读:
    js动态给对象添加事件
    JavaScript中提供获取HTML元素位置的属性:
    javascript:是什么意思
    Vector与ArrayList区别
    Java核心类库——集合的迭代(遍历) Iterator接口
    实例1(文字的放大缩小)
    基本控件的使用
    关于Android那些事
    初识Android
    JavaScript(Two)
  • 原文地址:https://www.cnblogs.com/cnDqf/p/14079524.html
Copyright © 2020-2023  润新知