🔗其他内容推荐🔗:动手实现多头注意力机制
Python 中的协程是一种高效的并发机制,通过 async
和 await
关键字,可以轻松实现异步编程。协程适用于 I/O 密集型任务,能够显著提高程序的效率和响应速度。然而,它也有局限性,例如不能处理 CPU 密集型任务。在实际开发中,需要根据具体需求选择合适的并发方式。
协程是用户态的轻量级线程,它们的创建和切换开销非常小。协程的上下文切换不需要操作系统内核的介入,因此可以快速地在协程之间切换。这使得协程在处理大量并发任务时更加高效。
A协程执行到某处后保存上下文等待B协程执行,B协程执行到某处后保存上下文将执行权给A等待A的执行,A和B协程同处于一个线程中,协同完成任务,切换代价小。当线程执行I/O 操作(如网络请求、文件读写)时,会主动释放 GIL 并进入阻塞状态,然后操作系统介入切换线程,代价较大。所以协程更适合处理I/O 密集型任务。
协程的优势:
- 高效:没有线程切换的开销
- 轻量:一个线程可运行成千上万个协程
- 可控:调度由程序员控制,避免竞态条件。因为当前协程在什么地方等待其它协程执行,以及什么情况下继续执行,都是程序员显示指定的。
- 节省资源:不需要多线程的锁机制
async
关键字:
-
定义协程函数:使用
async def
定义一个协程函数。协程函数与普通函数不同,它不能直接执行,而是需要通过事件循环或await
调用。async def my_coroutine(): print("Hello") await asyncio.sleep(1) # 模拟异步操作 print("World")
- 在这里,
my_coroutine
是一个协程函数,而不是普通的函数。 - 如果直接调用
my_coroutine()
,它不会执行,而是返回一个协程对象。
- 在这里,
await
关键字:
-
暂停协程执行:
await
用于暂停当前协程的执行,直到等待的异步操作完成。 -
等待异步操作:
await
后面必须是一个可等待对象(如协程对象、asyncio.Future
、asyncio.Task
等)。await asyncio.sleep(1) """ 这是一个异步的睡眠函数,它不会阻塞当前线程,而是挂起当前的协程(coroutine)。在挂起期间,其他协程可以继续运行,从而充分利用 CPU 时间。 而time.sleep()是一个同步的睡眠函数,它会阻塞当前线程 1 秒钟。如果在多线程程序中使用,意味着将切换到其它线程。 """
- 当执行到
await asyncio.sleep(1)
时,当前协程会暂停执行,让出控制权。 - 一旦
asyncio.sleep(1)
完成,协程会从暂停的地方继续执行。
- 当执行到
事件循环:
- Python 的异步编程依赖于事件循环(Event Loop)。
- 事件循环负责调度协程的执行,管理 I/O 操作,并在合适的时机恢复协程的执行。
- 事件循环的生命周期通常由
asyncio.run()
管理。
协程的生命周期:
- 创建协程:使用
async def
定义协程函数,调用协程函数会返回一个协程对象。 - 暂停执行:在协程中使用
await
暂停执行,等待异步操作完成。 - 恢复执行:当异步操作完成时,事件循环会恢复协程的执行。
Demo1:
import asyncio
import aiofiles
# 异步写入文件
async def write_file(filename, content):
async with aiofiles.open(filename, 'w') as file: # 异步打开文件
await file.write(content) # 异步写入内容
# 异步读取文件
async def read_file(filename):
async with aiofiles.open(filename, 'r') as file: # 异步打开文件
content = await file.read() # 异步读取内容
print(content)
# 主协程函数
async def main():
filename = "example.txt"
content = "Hello, World!"
await write_file(filename, content) # 异步写入文件
await read_file(filename) # 异步读取文件
print("异步操作已完成")
# 运行主协程
asyncio.run(main())
aiofiles.open()
是一个异步操作,用于打开文件。await file.write(content)
和await file.read()
分别用于异步写入和读取文件内容。
Demo2:
import asyncio
# 模拟从网站获取数据的协程
async def fetch_data(url, delay):
print(f"Starting to fetch data from {url}")
await asyncio.sleep(delay) # 模拟异步 I/O 操作
print(f"Finished fetching data from {url}")
return f"Data from {url}"
# 模拟处理数据的协程
async def process_data(data):
print(f"Processing {data}")
await asyncio.sleep(1) # 模拟数据处理的延迟
print(f"Processed {data}")
# 主协程,负责并发执行多个任务
async def main():
urls = [
("http://example.com", 2),
("http://example.org", 3),
("http://example.net", 1)
]
# 创建一个任务列表
tasks = []
for url, delay in urls:
task = asyncio.create_task(fetch_data(url, delay)) # 创建协程任务
tasks.append(task)
# 并发执行所有任务
"""
asyncio.gather 会同时启动所有传入的任务,并将它们交给事件循环管理.
事件循环是异步编程的核心机制。它负责:
调度协程:事件循环会维护一个任务队列,队列中的任务是需要运行的协程。
处理 I/O 事件:事件循环会监听 I/O 事件(如网络请求完成、文件读写完成等),并在事件发生时唤醒相应的协程。
切换协程:当一个协程挂起时,事件循环会切换到其他就绪的协程运行。
"""
results = await asyncio.gather(*tasks) # 等待所有任务完成并获取结果
# 处理每个任务的结果
for data in results:
await process_data(data)
# 运行主协程
asyncio.run(main())
if __name__ == '__main__':
pass