Python事件循环异常行为疑问及GIL相关困惑
关于Python异步代码与GIL的疑问
问题代码
import asyncio async def get_some_values_from_io(): print("Getsome value Executing...") await asyncio.sleep(3) return [100,200] vals = [] async def fetcher(): while True: print("Fetcher Executing...") io_vals = await get_some_values_from_io() for val in io_vals: vals.append(io_vals) async def monitor(): while True: print("Monitor Executing...") print (len(vals)) await asyncio.sleep(3) async def main(): t1 = asyncio.create_task(fetcher()) t2 = asyncio.create_task(monitor()) await asyncio.gather(t1, t2) asyncio.run(main()) print("Rest of the method is executing....")
运行现象
两个异步函数均调用asyncio.sleep(3),预期两者休眠时最后一条print("Rest of the method is executing....")会执行,但实际程序持续循环输出:
Fetcher Executing... Getsome value Executing... Monitor Executing... 0 Fetcher Executing... Getsome value Executing... Monitor Executing... 2 ...
疑问解答
1. 为什么最后一条print语句不会执行?
asyncio.run(main())会阻塞当前线程直到传入的协程(即main)执行完毕。但main里通过asyncio.gather等待的t1和t2都是while True的无限循环协程,永远不会结束,所以asyncio.run永远不会返回,自然不会执行后面的print语句。
asyncio.sleep只是让当前协程暂停,把控制权交还给事件循环,让其他协程有机会运行,但事件循环本身仍被asyncio.run掌控,主线程被事件循环完全占据,不会向下执行后续代码。
2. 关于GIL与事件循环的理解
你的核心理解是正确的:默认情况下,Python异步事件循环运行在单线程中,并且受GIL限制——同一时间只有一个线程能执行Python字节码。
3. run_in_executor与GIL的关系
run_in_executor确实会把任务放到事件循环外的线程(或进程)中运行,这和GIL的特性并不矛盾:
- GIL的限制是同一时间只有一个线程能执行Python代码,但当线程执行IO密集型任务(比如调用C扩展的IO操作时会主动释放GIL)或非Python代码时,GIL会被释放,其他线程可以获取GIL执行Python代码。
run_in_executor默认使用ThreadPoolExecutor,线程池中的线程执行CPU密集型任务时,每个线程仍受GIL限制,但因为这些线程在事件循环之外,它们的执行不会阻塞事件循环运行——事件循环可以继续处理其他异步协程,线程池中的线程则在各自时间片内获取GIL执行任务。- 如果是CPU密集型任务,更推荐通过
run_in_executor指定ProcessPoolExecutor,因为进程拥有独立的GIL,多个进程可以真正并行执行Python代码,避开GIL的限制。
内容的提问来源于stack exchange,提问作者user3103957
相关产品推荐
相关产品推荐

