您的位置:首页 >Python多线程与asyncio协同实战
发布于2026-02-26 阅读(0)
扫一扫,手机访问
asyncio.run()不可在已有事件循环的线程中重复调用;多线程协程混合应使用run_in_executor或to_thread;threading.local()不适用于协程上下文,需改用contextvars;日志需避免await表达式和阻塞IO。

主线程调用 asyncio.run() 后,事件循环在主线程中运行且被独占;此时若另起 threading.Thread 并在其中调用 asyncio.run(),会报 RuntimeError: asyncio.run() cannot be called from a running event loop —— 因为子线程虽独立,但如果你误在主线程的 loop 还没结束时又触发一次 asyncio.run(),就会撞上这个限制。
实操建议:
asyncio.run() 启动主协程,再用 loop.run_in_executor() 把阻塞操作(如文件读写、旧版同步 SDK 调用)扔进线程池,而不是手动建 Threadasyncio.run_coroutine_threadsafe(coro, loop),并确保传入的是主线程的 loop 实例(通常需提前保存)asyncio.get_event_loop():它在非主线程默认返回新 loop,但该 loop 未运行,直接 run_until_complete() 会卡住或报错Python 的 threading.local() 靠线程 ID 做数据隔离,而 asyncio 协程常在同一线程内切换执行——所以你在协程 A 里给 local.x = 1,协程 B 可能读到它,除非你显式绑定到任务生命周期。
实操建议:
threading.local() 存储 request-id、db connection 等上下文敏感数据,协程调度会让它“泄漏”contextvars.ContextVar:它感知协程切换,var.set() 和 var.get() 自动绑定当前 tasklocal.x = copy.deepcopy(local.x),但性能差且易漏,不推荐以前常用 loop.run_in_executor(None, sync_func, *args) 做 CPU/IO 密集型操作的异步封装,但它要手动管理 loop 引用,且在 asyncio.run() 结束后 loop 已关闭,再调用会出 RuntimeError: Event loop is closed。
实操建议:
await asyncio.to_thread(sync_func, *args):它自动选可用线程池,且不依赖用户传 loop,更健壮to_thread() 仅适用于 IO 密集型或短时 CPU 操作;长耗时 CPU 计算仍应走 ProcessPoolExecutor,否则会堵住整个 event loopanyio 或手写兼容 wrapper,但别硬套 run_in_executor 到任意位置——尤其别在 __aexit__ 或 signal handler 里调用,loop 可能已 teardown标准 logging 模块本身线程安全,但如果你在协程里用 logging.info() 打印了带 await 表达式的字符串(比如 f"result={await api_call()}"),那实际是先 await 再拼接再 log,中间可能被切走;更隐蔽的是,自定义 Handler 若含阻塞 IO(如写文件、发 HTTP),会拖慢整个 loop。
实操建议:
logging.xxx() 参数里写 await 或耗时表达式logging.handlers.QueueHandler + 后台线程消费,把 IO 移出 event loopcontextvars + 自定义 Logger.filter() 注入,别靠 threading.local() 拼凑协程和线程的边界比看起来薄得多,一个 await 调用背后可能藏着 loop 切换、线程池调度、context 变更——别假设“只要没用 time.sleep() 就还是 async”。
上一篇:12306临时身份证申请流程详解
下一篇:最右怎么查看私信?
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9