协程通过asyncio实现单线程内高效并发,利用事件循环在IO等待时切换任务,避免线程开销,提升资源利用率与并发性能。

协程(Coroutine)与 Python 的 asyncio 库在处理 IO 密集型任务时,提供了一种极其高效且优雅的并发解决方案。它允许程序在等待外部操作(如网络请求、文件读写)完成时,切换到其他任务,从而充分利用 CPU 时间,显著提升应用响应速度和吞吐量,而非传统意义上的多线程或多进程并发。
协程和
asyncio
asyncio
async
await
await
一个简单的例子,假设我们要同时请求多个网页:
import asyncio
import time
async def fetch_url(url):
print(f"开始请求: {url}")
# 模拟一个网络IO操作,实际中会用aiohttp等库
await asyncio.sleep(2) # 假设网络请求需要2秒
print(f"完成请求: {url}")
return f"数据来自 {url}"
async def main():
urls = ["http://example.com/page1", "http://example.com/page2", "http://example.com/page3"]
start_time = time.monotonic()
# 使用asyncio.gather并发执行所有协程
results = await asyncio.gather(*[fetch_url(url) for url in urls])
end_time = time.monotonic()
print(f"\n所有请求完成,耗时: {end_time - start_time:.2f} 秒")
for res in results:
print(res)
if __name__ == "__main__":
asyncio.run(main())这段代码中,
fetch_url
asyncio.gather
asyncio
说实话,我个人觉得这是
asyncio
首先是上下文切换的开销。线程的上下文切换是由操作系统内核调度的,这意味着每次切换都需要保存和恢复大量的寄存器状态、程序计数器等,这个过程是比较“重”的。如果你的应用需要成百上千甚至上万个并发连接,那么频繁的线程切换会消耗大量的 CPU 时间,甚至可能导致“颠簸”,性能反而下降。协程则不同,它的上下文切换是用户态的,由程序自身(通过事件循环)协作完成,仅仅是保存和恢复一些必要的栈帧信息,这个过程非常“轻量”,开销几乎可以忽略不计。
其次是内存占用。每个线程都需要独立的栈空间,通常是几兆字节。当并发量达到数千时,线程所需的总内存会非常可观,可能导致内存溢出。而协程共享同一个线程的栈空间,每个协程的内存占用非常小,通常只有几十到几百字节。这意味着在相同的内存资源下,协程能够支持远超线程的并发数量。
再来就是编程模型和复杂性。虽然初学
asyncio
async
await
await
最后,尽管 Python 的 GIL(全局解释器锁)限制了同一时刻只有一个线程能执行 Python 字节码,使得多线程在 CPU 密集型任务中效果不佳,但对于 IO 密集型任务,当线程在等待 IO 完成时,GIL 会被释放,其他线程可以继续执行。协程同样在等待 IO 时释放控制权,其效率优势在于上述的轻量级切换和低内存占用,而非绕过 GIL。在我看来,对于网络服务、爬虫、API 网关这类以等待外部响应为主的应用,协程简直是天作之合。
要用
asyncio
首先,拥抱 async
await
async def
await
await
async def
# 示例:一个简单的异步函数
async def process_data(data):
print(f"开始处理数据: {data}")
await asyncio.sleep(1) # 模拟耗时操作
print(f"数据处理完成: {data}")
return f"处理结果 for {data}"其次,启动事件循环。你的异步应用总得有个入口。在 Python 3.7+ 中,最简单的方式是使用
asyncio.run()
async def main_application():
# 这里可以调度多个协程
await process_data("item1")
if __name__ == "__main__":
asyncio.run(main_application())再次,并发执行多个协程。如果你有多个独立的 IO 密集型任务需要同时进行,
asyncio.gather()
async def main_concurrent():
tasks = [
process_data("itemA"),
process_data("itemB"),
process_data("itemC")
]
results = await asyncio.gather(*tasks) # 星号解包列表为单独参数
print("\n所有并发任务结果:", results)
if __name__ == "__main__":
asyncio.run(main_concurrent())一个非常重要的点是处理阻塞调用。这是
asyncio
time.sleep()
asyncio.sleep()
requests.get()
aiohttp.ClientSession().get()
loop.run_in_executor()
import concurrent.futures
import requests
def blocking_io_call(url):
print(f"开始同步请求: {url}")
response = requests.get(url) # 这是一个阻塞调用
print(f"完成同步请求: {url}")
return response.status_code
async def fetch_with_executor(url):
loop = asyncio.get_running_loop()
# 在默认的ThreadPoolExecutor中运行阻塞函数
# 这样就不会阻塞主事件循环
status_code = await loop.run_in_executor(
None, # 使用默认的线程池
blocking_io_call,
url
)
print(f"URL: {url}, Status: {status_code}")
return status_code
async def main_with_blocking_calls():
urls = ["https://www.google.com", "https://www.python.org"]
tasks = [fetch_with_executor(url) for url in urls]
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main_with_blocking_calls())最后,使用异步友好的库。为了充分发挥
asyncio
async/await
aiohttp
asyncpg
aiomysql
websockets
asyncio
在实际项目中,协程和
asyncio
常见应用场景:
asyncio
asyncio
aiohttp
asyncio
websockets
asyncpg
asyncio
asyncio
注意事项:
asyncio
run_in_executor
await
gather
try...except
asyncio
python -X dev -m asyncio your_script.py
asyncio
asyncio
multiprocessing
run_in_executor
asyncio
async/await
run_in_executor
aiohttp
ClientSession
async with
总之,
asyncio
以上就是协程(Coroutine)与 asyncio 库在 IO 密集型任务中的应用的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号