前言
在之前Q群ChatGPT机器人使用的依赖仓库中,作者更新了V2 Fast ChatGPT API
的用法(截至此时该方法已失效),里面涉及到了协程的相关用法。协程在平时用到的不多,正好趁机补充补充知识。
协程基础概念
协程(coroutine)又称微线程,是一中轻量级的线程,它可以在函数的特定位置暂停或恢复,同时调用者可以从协程中获取状态或将状态传递给协程。进程和线程都是通过CPU的调度实现不同任务的有序执行,而协程是由用户程序自己控制调度的,也没有线程切换的开销,所以执行效率极高[1]。
协程主要具有以下优势[2]: 1.协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。 2.就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。 3.把一个IO操作 写成一个协程。当触发IO操作的时候就自动让出CPU给其他协程。要知道协程的切换很轻的。 协程通过这种对异步IO的封装 既保留了性能也保证了代码的容易编写和可读性
协程的适用场景: 协程适用于I/O密集型而非计算密集型场景。在协程发起I/O请求后返回结果前往往有大量闲置时间——该时间可能用于网络数据传输、获取协议头、服务器查询数据库等,而I/O请求本身并不耗时,因此协程可以发送一个请求后让渡给系统干别的事,这就是协程提高性能的原因[3]。
协程发展历史
- 在python2以及python3.3之前,使用协程要基于greenlet或者gevent这种第三方库来实现,由于不是Python原生封装的,使用起来可能会有一些性能上的流失。
- 在python3.4中,引入了标准库asyncio,直接内置了对异步IO的支持,可以很好的支持协程。
- 从Python3.5开始引入了新的语法async和await,把asyncio库的
@asyncio.coroutine
替换为async
,把yield from
替换为awaits
- Python3.7中加入了
asyncio.create_task()
函数,用于创建任务队列。
下面就用Python3.8来进行学习。
协程使用
async
async
关键字定义了一个协程函数。
协程函数和普通的函数不一样,不能直接执行。必须将协程对象放入事件循环中来执行。
下面是一个示例:
代码语言:javascript复制import asyncio
async def fun():
print(1)
xc = fun() # 生成一个协程对象
# 执行协程函数,必须将协程对象放入事件循环之中。
# Python3.7之前运行协程的方式
# loop = asyncio.get_event_loop() # 获取一个事件循环
# loop.run_until_complete(xc) # 将协程对象放入任务列表
# Python3.7之后,可以使用下面的方式运行协程函数。
asyncio.run(xc)
await
await
作用是等待可等待对象。
可等待对象包含协程对象,future对象,task对象,示例:
import asyncio
async def func1():
print("start")
await func2()
print("end")
async def func2():
i = 0
while True:
if i < 10:
print(i)
i = 1
else:
break
asyncio.run(func1())
这里的func1执行中途等待func2执行完毕再输出end,因此执行结果为:
start 0 1 2 3 4 end
task对象
在上面的示例中,func1一直等待func2执行结束再运行。如果需要不等待继续执行,可以将两个协程一起封装成一个task对象。 示例:
代码语言:javascript复制import asyncio
async def func1():
print("start")
await asyncio.sleep(1) # 注意,这里不在是等待func2对象,而是睡眠1s
print("end")
async def func2():
i = 0
while True:
if i < 5:
print(i)
await asyncio.sleep(1)
i = 1
else:
break
task_list = [func1(), func2()] # 构造一个任务列表
t1 = asyncio.wait(task_list) # 构造成为task对象
asyncio.run(t1) # 执行
输出:
start 0 end 1 2 3 4
注:这里两个函数是交替执行的,func1先输出start,遇到await
,切换成func2,再遇到await
,切换回func1。
在python3.7之后,可以使用asyncio.create_task()
实现类似的效果,示例:
import asyncio
async def func1():
print("start")
await asyncio.sleep(1) # 注意,这里不在是等待func2对象,而是睡眠1s
print("end")
async def func2():
i = 0
while True:
if i < 5:
print(i)
await asyncio.sleep(1)
i = 1
else:
break
async def main():
# 将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
c1 = asyncio.create_task(func1(), name='c1')
c2 = asyncio.create_task(func2(), name='c2')
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
# 此处的await是等待相对应的协程全都执行完毕并获取结果,主要是为了方便观察。
await c1 # 等待task对象
await c2
asyncio.run(main())
获取协程返回值
获取协程返回值至少有4种方式[5],这里学习一种,下面是示例:
代码语言:javascript复制import asyncio
async def func1(input1):
return str(input1) "output1"
if __name__ == '__main__':
loop = asyncio.get_event_loop()
task = loop.create_task(func1("abc"))
loop.run_until_complete(task)
output = task.result()
print(output)
参考
[1] Python协程讲解 https://blog.csdn.net/weixin_50097774/article/details/121443503 [2] Python教程:协程 https://blog.csdn.net/qdPython/article/details/127892251 [3] Python 进程、线程、协程傻傻分不清楚?详细总结(附代码) https://blog.csdn.net/FRIGIDWINTER/article/details/124369567 [4] Python协程 https://blog.csdn.net/zy010101/article/details/121796453 [5] Python:获取协程返回值的四种方式 https://blog.csdn.net/hzblucky1314/article/details/121964346