理解asyncio运行原理,首先我们绕不开的概念就是要理解并发和并行
1. 并发:
-
并发是指系统可以同时管理多个任务,但这些任务并不真正同时运行。
-
在 Python 的
asyncio
中,所有任务运行在单个线程和单个 CPU 核心上,通过事件循环在任务之间快速切换,当某个任务遇到 I/O(例如等待文件读写或网络请求)时,其他任务可以继续执行。
2. 并行:并行指真正的同时执行多个任务,比如利用多线程或多进程让多个任务同时在多个 CPU 核心上运行
3. asyncio是并发还是并行?直接给答案: 是并发。因为asyncio
使用的是单线程事件循环,虽然它能通过任务间的切换实现“同时运行”的效果,但本质上同一时刻只有一个任务在运行,asyncio
不会使用多个线程或多个 CPU 核心,因此它不是并行的。
4. 为什么asyncio只能使用单 CPU 核心?我主机有8核,为什么不能使用?
因为asyncio
本质上是一种 协程 的实现,依赖单线程的 事件循环,即使你的主机有 8 核,asyncio
依然只能使用 一个线程,因此只能运行在一个 CPU 核心上
5. 为什么协程 就只能在一个CPU核心上运行?要回答这个问题,就不得不说协程的本质了。
-
协程的本质
协程是一种轻量级的用户态线程,它依赖 事件循环 来管理任务的调度和切换,所有协程都在同一个线程中运行。
-
单线程事件循环: 协程是基于单线程的机制,所有任务的执行都由同一个事件循环调度,因此,事件循环只能运行在一个 CPU 核心上,协程的运行也被限制在这个核心中。
-
非抢占式调度: 协程的调度是 非抢占式 的,即任务之间的切换是由代码中的
await
、yield
等显式控制的,只有当任务主动交出控制权时,事件循环才会切换到其他协程运行。
这意味着协程只能依赖单线程,无法直接利用多核 CPU 的并行计算能力
6. 如何使用asyncio,把主机多核数利用起来
# 1. 多进程 + 协程
# 通过 multiprocessing 模块创建多个进程,每个进程运行自己的事件循环,从而利用多个核心,因为每个进程都有独立的 Python 解释器和 GIL,因此可以并行运行在多个核心上。
import asyncio
from multiprocessing import Process
async def async_task(name):
print(f"{name} 开始")
await asyncio.sleep(1) # 模拟 I/O 操作
print(f"{name} 完成")
def run_event_loop():
asyncio.run(async_task("任务"))
if __name__ == "__main__":
processes = []
for i in range(4): # 启动 4 个进程
p = Process(target=run_event_loop)
processes.append(p)
p.start()
for p in processes:
p.join()
# 2. 线程池/进程池 + 协程
# 可以通过 concurrent.futures 模块的线程池或进程池,将部分任务分发到其他线程或进程中运行。协程负责 I/O 密集型任务,CPU 密集型任务由进程池并行执行,从而实现多核利用
import asyncio
from concurrent.futures import ProcessPoolExecutor
async def main():
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(pool, cpu_bound_task, 10)
print(f"计算结果: {result}")
def cpu_bound_task(n):
# 模拟 CPU 密集型任务
total = 0
for i in range(10**8):
total += i * n
return total
asyncio.run(main())