【Python】协程学习笔记

2023-03-09 18:32:47 浏览数 (1)

前言

在之前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对象,示例:

代码语言:javascript复制
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()实现类似的效果,示例:

代码语言: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


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

0 人点赞