1. 协程
协程(Coroutine)是计算机程序组件,它允许执行的暂停和恢复,通常用于并发编程
。协程可以在执行过程中挂起,等待外部事件,然后在事件完成后恢复执行,而不像线程那样需要操作系统进行上下文切换。协程的这种特性使得它在处理 I/O 密集型任务
(例如,网络请求、文件读写等操作)时非常高效,因为它避免了线程切换的开销。协程没有用户态
和内核态
之间的切换。
在 Python 中,协程是通过async def
关键字定义的函数。协程函数内部可以使用await
关键字来挂起当前协程的执行,等待另一个协程或异步操作完成。
协程的主要特点
- 挂起和恢复:协程可以在执行过程中暂停,等待外部事件(如 I/O 操作),然后在事件完成后恢复执行。
- 高效:由于协程的挂起和恢复是在用户空间进行的,不需要操作系统的上下文切换,因此比线程更高效。
- 协作式多任务:协程通过协作来实现多任务,即一个协程在挂起之前会确保其他协程有机会执行。
协程的基本使用
2. asyncio
asyncio.run
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(2) # 模拟耗时操作
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(1) # 模拟耗时操作
print("Task 2 finished")
async def task3():
print("Task 3 started")
await asyncio.sleep(3) # 模拟耗时操作
print("Task 3 finished")
async def main():
# 创建任务
# 创建task对象,将当前任务添加到事件循环
task_1 = asyncio.create_task(task1())
task_2 = asyncio.create_task(task2())
task_3 = asyncio.create_task(task3())
# 等待所有任务完成
await asyncio.gather(task_1, task_2, task_3)
# 也可以用await asyncio.wait实现
# done, pending = await asyncio.wait([task1, task2, task3])
# 运行事件循环
asyncio.run(main())
代码解释
-
定义三个协程:
- task1、task2 和 task3 分别代表三个并发任务。
- 每个任务内部使用
await asyncio.sleep(n)
来模拟耗时操作,n 表示耗时的秒数。
-
定义主协程 main:
- 使用
asyncio.create_task
将每个任务包装成一个任务对象。 - 使用
asyncio.gather
并发执行所有任务,并等待它们完成。
- 使用
-
运行事件循环:
- 使用
asyncio.run(main())
运行主协程,启动事件循环并执行所有任务。
- 使用
运行结果
运行上述代码后,你会看到类似如下的输出:
Task 1 started
Task 2 started
Task 3 started
Task 2 finished
Task 1 finished
Task 3 finished
asyncio.get_event_loop
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(2) # 模拟耗时操作
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(1) # 模拟耗时操作
print("Task 2 finished")
async def task3():
print("Task 3 started")
await asyncio.sleep(3) # 模拟耗时操作
print("Task 3 finished")
def main():
loop = asyncio.get_event_loop()
try:
# 创建任务
tasks = [
# 创建task对象,将当前任务添加到事件循环
loop.create_task(task1()),
loop.create_task(task2()),
loop.create_task(task3())
]
# 运行事件循环,直到所有任务完成
loop.run_until_complete(asyncio.gather(*tasks))
finally:
loop.close()
if __name__ == "__main__":
main()
代码解释
-
定义三个协程:
- task1、task2 和 task3 分别代表三个并发任务。
- 每个任务内部使用
await asyncio.sleep(n)
来模拟耗时操作,n 表示耗时的秒数。
-
定义主函数 main:
- 获取事件循环 loop。
- 使用
loop.create_task
将每个任务包装成一个任务对象,并存储在 tasks 列表中。 - 使用
loop.run_until_complete(asyncio.gather(*tasks))
运行事件循环,直到所有任务完成。 - 在 finally 块中关闭事件循环
loop.close()
。
-
运行主函数:
在 if __name__ == "__main__"
块中调用 main() 函数。
run和get_event_loop的区别
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
和
asyncio.run(main())
的效果是一样的,后者是python3.7的实现方式
await asyncio.wait 和asycio.gather的区别
await asyncio.wait()
和 asyncio.gather()
都是用于等待多个协程完成的工具,但它们之间有一些关键区别:
-
功能差异:
asyncio.gather()
:将多个协程作为参数传入,启动并等待所有协程完成,返回所有协程的结果(以相同顺序)。如果其中任何一个协程抛出异常,gather()
会传播该异常。asyncio.wait()
:接收一个可迭代的协程(如列表),并根据指定的等待条件(return_when
参数)返回完成的和未完成的任务。它返回的是一个元组,包含完成的和未完成的任务集合。
-
返回结果:
asyncio.gather()
返回协程的结果列表。asyncio.wait()
返回已完成和未完成任务的集合元组(done, pending)
。
-
异常处理:
asyncio.gather()
遇到异常时会传播异常并终止执行。asyncio.wait()
默认不会传播异常,而是将异常作为任务的一部分返回,除非你手动处理任务结果。
-
使用场景:
asyncio.gather()
更适合在需要获取所有任务结果的情况下使用。asyncio.wait()
更适合需要更灵活控制任务的场景,比如等待第一个任务完成或检查是否有任务超时等。