感谢大佬分享
除了多进程,多线程之后, 还有异步编程可以提升性能.
前言
在 Python 3.8 以后的版本中,异步编程变得越来越重要。本文将系统介绍 Python 标准库中的异步编程工具,带领大家掌握 async/await 语法和 asyncio 的使用。
1/从一个简单的场景开始
import time
import random
def process_item(item):
# 模拟耗时操作
print(f"处理中:{item}")
process_time = random.uniform(0.5, 2.0)
time.sleep(process_time)
return f"处理完成:{item},耗时 {process_time:.2f} 秒"
def process_all_items():
items = ["任务A", "任务B", "任务C", "任务D"]
results = []
for item in items:
result = process_item(item)
results.append(result)
return results
if __name__ == "__main__":
start = time.time()
results = process_all_items()
end = time.time()
print("\n".join(results))
print(f"总耗时:{end - start:.2f} 秒")
# 输出结果
处理中:任务A
处理中:任务B
处理中:任务C
处理中:任务D
处理完成:任务A,耗时 1.97 秒
处理完成:任务B,耗时 1.28 秒
处理完成:任务C,耗时 0.66 秒
处理完成:任务D,耗时 1.80 秒
总耗时:5.72 秒
这段代码的问题很明显:每个任务都必须等待前一个任务完成才能开始。如果有4个任务,每个任务平均耗时1秒,那么总耗时就接近4秒。
也就是说, 这些任务之间是串行的, 完成一个之后再去执行另一个. 如果任务很多,那么就会很耗时.
2/认识async/ await
Python 引入了 async/await 语法来支持异步编程。
当我们在函数定义前加上 async 关键字时,这个函数就变成了一个"协程"(coroutine)。
而 await 关键字则用于等待一个协程完成。让我们改写上面的代码:
import asyncio
import random
import time
async def process_item(item):
print(f"处理中:{item}")
# async 定义的函数变成了协程
process_time = random.uniform(0.5, 2.0)
# time.sleep() 换成 asyncio.sleep()
await asyncio.sleep(process_time) # await 等待异步操作完成
return f"处理完成:{item},耗时 {process_time:.2f} 秒"
async def process_all_items():
items = ["任务A", "任务B", "任务C", "任务D"]
# 创建任务列表
tasks = [
asyncio.create_task(process_item(item))
for item in items
]
print("开始处理")
results = await asyncio.gather(*tasks) # 并发运行多个任务, 并等待它们全部完成
return results
async def main():
start = time.time()
results = await process_all_items()
end = time.time()
print("\n".join(results))
print(f"总耗时:{end - start:.2f} 秒")
if __name__ == "__main__":
asyncio.run(main())
# 输出
开始处理
处理中:任务A
处理中:任务B
处理中:任务C
处理中:任务D
处理完成:任务A,耗时 1.97 秒
处理完成:任务B,耗时 0.80 秒
处理完成:任务C,耗时 0.83 秒
处理完成:任务D,耗时 1.46 秒
总耗时:1.97 秒
可以看到,这里的总耗时只有1.97秒, 是其中耗时最大的一个子任务的执行时间.
让我们详细解释这段代码的执行过程:
- 当函数被
async关键字修饰后,调用该函数不会直接执行函数体,而是返回一个协程对象 - await 关键字只能在
async函数内使用,它表示"等待这个操作完成后再继续" asyncio.create_task()将协程包装成一个任务,该任务会被事件循环调度执行asyncio.gather()并发运行多个任务,并等待它们全部完成asyncio.run()创建事件循环,运行main()协程,直到它完成
3/使用 asyncio.wait_for 添加超时控制
在实际应用中,我们往往需要为异步操作设置超时时间:
import asyncio
import random
import time
async def process_item(item):
process_time = random.uniform(0.5, 2.0)
try:
# 设置1秒超时
await asyncio.wait_for(
asyncio.sleep(process_time),
timeout=1.0
)
return f"处理完成:{item},耗时 {process_time:.2f} 秒"
except asyncio.TimeoutError:
return f"处理超时:{item}"
async def main():
items = ["任务A", "任务B", "任务C", "任务D"]
tasks = [
asyncio.create_task(process_item(item))
for item in items
]
start = time.time()
results = await asyncio.gather(*tasks, return_exceptions=True)
end = time.time()
print("\n".join(results))
print(f"总耗时:{end - start:.2f} 秒")
if __name__ == "__main__":
asyncio.run(main())
# 输出
处理超时:任务A
处理完成:任务B,耗时 0.94 秒
处理超时:任务C
处理完成:任务D,耗时 0.78 秒
总耗时:1.00 秒