30. 协程
1.协程的概念
1.1 定义
进程是操作系统内部运行的程序
线程是进程内部运行的程序
协程是线程内部运行的程序
协程是单线程下的并发,又成微线程,英文名coroutine
1.2 协程的优点
协程切换的开销更小
GIL锁导致同一时刻只能运行一个线程,一个线程内不会限制协程数,单线程就可以实现并发的效果,最大程度利用CPU
1.3 协程的缺点
协程的本质是单线程下的多任务处理,无法利用多核优势
如果协程发生阻塞,在没有使用异步I/O的情况下,那么整个线程将阻塞,所在线程的其它协程任务都不能运行
2. 协程的操作
2.1 协程函数
加上async关键字的函数就是协程函数
async def f1():
...
2.2 协程对象
协程函数调用后的返回值就是协程对象
async def f1():
print(111)
res = f1()
print(res)
按一般函数的调用方法调用协程函数不会报错,但是会发出警告
按一般函数的调用方法调用协程函数,函数内部的代码不会执行,只是会返回一个协程对象。
2.3 asyncio模块
Python 3.4:asyncio 被正式引入标准库,是一个实现异步编程的模块。
Python 3.5:引入了 async 和 await 关键字,这使得协程的定义和使用更加直观和简洁。这些关键字取代了早期版本中的 @asyncio.coroutine 装饰器和 yield from 语法。
2.4 事件循环的概念
事件循环可以类比while循环来理解,在循环周期内运行一些任务,特定的条件下结束循环。
import asyncio
loop = asyncio.get_event_loop()
2.5 协程函数的两种调用方法,以解释器3.10版本为例
由协程对象的定义可知,按函数名( )无法调用协程函数,需要协程对象和事件循环配合才能实现
[1]方法一:
import asyncio
async def work():
print(666)
return 'work函数的返回值'
def create_coroutine():
obj = work() # 1.调用协程函数,生成协程对象
circle = asyncio.get_event_loop() # 2.建立一个事件循环
circle.run_until_complete(obj) # 3.将协程对象当作任务提交到事件循环的任务列表中,协程运行完成事件循环停止
if __name__ == '__main__':
create_coroutine()
在 Python 3.10 中,这个警告是因为在没有活动事件循环的主线程中调用 asyncio.get_event_loop() 时,get_event_loop() 会自动创建一个新的事件循环,这种方式被认为是不推荐的。
为了避免这个警告,可以使用 asyncio.run() 来运行协程,这是从 Python 3.7 开始推荐的做法。asyncio.run() 会自动创建和关闭事件循环,简化了异步代码的编写。
[2]方法二:
import asyncio
async def work():
print(666)
return 'work函数的返回值'
def create_coroutine():
obj = work() # 1.调用协程函数,生成协程对象
asyncio.run(obj) # 2.调用run函数启动协程对象
if __name__ == '__main__':
create_coroutine()
方式二的本质和方式一是一样的,内部先建立事件循环,然后运行run_until_complete
需要注意的是,run函数在解释器3.7才加入
2.6 await关键字
await关键字用于等待一个async(异步)函数的结果,使用await可以让程序在等待某个操作完成的同时不阻塞整个事件循环,从而允许其它任务运行,对于I/O密集型的应用特别有用。
await关键字解决了一个协程阻塞而导致整个线程阻塞的问题。
注意事项:
1.使用await必须在async(异步)函数中,不能在非异步函数中使用。
2.await只能用于异步函数返回的协程对象。
3.在异步函数内部,await可以用来等待另一个异步函数的结果,这会暂停当前协程的运行,直到等待的协程完成,然后继续运行。
代码示例
import asyncio
async def f1():
print('异步函数运行开始')
await asyncio.sleep(1) # 模拟异步操作,比如网络请求
print('异步函数运行结束')
return 666
async def f2():
res = await f1()
print(res)
asyncio.run(f2()) # 获取事件循环并运行f2()协程
分析:
f1是一个异步函数,使用了await来等待asyncio.sleep(1)。这实际上并不会让程序等待1秒,而是允许事件循环在这1秒内执行其它任务。
f2也是一个异步函数,等待f1的运行结果。
ascyncio.run(f2())启动事件循环并运行f2协程。
2.7 Task对象
[1]概念
前面的协程代码都只创建了一个任务,即事件循环的列表中只有一个任务对象;如需在程序中创建多个任务对象,需要使用Task。
Task用于并发调度协程,通过asyncio.create(协程对象)的方式创建Task对象,可以让协程加入事件循环中等待被调度执行。
注意事项:
asyncio.create_task( )函数在python3.7中被加入,在之前的版本可以改用底层级的asyncio.ensure_future( )函数。
[2]创建多任务方式一
逐个创建任务
import asyncio
# 定义协程功能函数
async def work():
print('协程运行开始')
await asyncio.sleep(1) # 模拟异步操作,比如网络请求
print('协程运行结束')
return 666
# 定义产生协程函数
async def create_coroutine():
print('产生协程的函数运行开始')
task1 = asyncio.create_task(work()) # 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环
task2 = asyncio.create_task(work()) # 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环
response1 = await task1 # 引用了await之后,task1遇到sleep不会阻塞整个线程
response2 = await task2
print(response1, response2)
print('产生协程的函数运行结束')
if __name__ == '__main__':
asyncio.run(create_coroutine())
[3]创建多任务方式二
使用列表生成式生成任务
import asyncio
# 定义协程功能函数
async def work(num):
print('协程运行开始')
await asyncio.sleep(1) # 模拟异步操作,比如网络请求
print('协程运行结束')
return num * num
# 定义产生协程函数
async def create_coroutine():
print('产生协程的函数运行开始')
# 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环
task_list = [asyncio.create_task(work(i)) for i in range(1, 4)]
# 引用了await之后,task1遇到sleep不会阻塞整个线程
# 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中
# wait里面要放可迭代对象
done, pending = await asyncio.wait(task_list, timeout=None)
print(done)
print(pending)
print('产生协程的函数运行结束')
if __name__ == '__main__':
asyncio.run(create_coroutine())
[3]获取协程返回值
在步骤[2]的基础上使用async.gather()获取返回值
import asyncio
# 定义协程功能函数
async def work(num):
print('协程运行开始')
await asyncio.sleep(1) # 模拟异步操作,比如网络请求
print('协程运行结束')
return num * 10
# 定义产生协程函数
async def create_coroutine():
print('产生协程的函数运行开始')
# 将work()得到的协程对象封装到Task对象中,并立即添加到事件循环的任务列表中,等待事件循环
task_list = [asyncio.create_task(work(i)) for i in range(1, 4)]
# 引用了await之后,task1遇到sleep不会阻塞整个线程
# 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中
# wait里面要放可迭代对象
done, pending = await asyncio.wait(task_list, timeout=None)
response = await asyncio.gather(*task_list)
print(response)
print('产生协程的函数运行结束')
if __name__ == '__main__':
asyncio.run(create_coroutine())
需要注意的是,以上代码即使不运行 done, pending = await asyncio.wait(task_list, timeout=None),也能获取到返回值。