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内核数量的上限影响的,一般的建议是不超过内核数量,但是在一些场景的限制下为了提高效率,单纯受限的多进程不能满足要求的时候,不妨将多进程的‘主进程’结合并发来提高效率。
由于没有实际测试性能,不能断言究竟哪种方式效率更高,毕竟也与任务内容有关。这只是一种思路,留待日后验证。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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 '\033[0;32mprocess {p_flag} :cost {time.time() - ts}\033[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}' ) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通