美文网首页
python协程(2):使用yield关键字实现协程

python协程(2):使用yield关键字实现协程

作者: 米那斯提力斯 | 来源:发表于2022-02-11 14:50 被阅读0次

一、什么是协程(coroutinr)

在学习写成之前、首先要明确一些概念,比如子程序、函数、并发、异步、多线程等等。

1、协程定义:
协程,又称微线程,纤程。英文名Coroutine。

2、子程序,或者称为函数。在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。

顺序执行的缺点:这里小伙伴们都应该是分清楚,那就是程序的无休止等待,必须等待一个函数执行完之后才返回结果。

3、多线程。避免顺序执行的方式之一是多线程,但是考虑到python语言的特性(GIL锁),再执行计算密集型的任务时,多线程的执行效果反而变慢,再执行IO密集型的任务时候虽然有不错的性能提升,但是依然会有线程管理与切换、同步的开销等等(具体原因这里不详细说明,请参见相关的GIL说明)

4、协程。协程有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

优势一:最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

优势二:就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

5、多进程+协程。因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

6、协程与一般函数的不同点

协程看上去也是子程序(函数),但执行过程中,在子程序内部(函数)可中断,而不是一次性一定要执行完才行,然后转而执行别的子程序,在适当的时候再返回来接着执行。

二、yield实现协程

在Python中,协程通过yield实现。因为当一个函数中有yield存在的时候,这个函数是生成器,那么当你调用这个函数的时候,你在函数体中写的代码并没有被执行,而是只返回了一个生成器对象,这个需要特别注意。然后,你的代码将会在每次使用这个生成器的时候被执行。

前面讲过yield表达式的两个关键作用:①返回一个值、②接收调用者的参数

“调用者”与“被调用者”之间的通信是通过send()进行联系的

正是因为yield实现的生成器具备“中断等待的功能”,才使得yield可以实现协程。

(1)实例一:

生产者-消费者模型。这里不讨论生产者-消费者模式到底有什么用,这里要实现的就是简单的函数调用。代码如下:

def consumer():
    r = ''
    while True:
        n = yield r  # 执行的中断点
        if not n:
            return
        print('[消费者] 正在消费:{0}'.format(n))
        r = '200 人民币'


def produce(c):
    c.send(None)  # 启动消费者(生成器)——实际上是函数调用,只不过生成器不是直接象函数那般调用的
    n = 0
    while n < 3:
        n = n + 1
        print('[生产者] 正在生产:{0}'.format(n))
        r = c.send(n)  # 给消费者传入值——实际上也是函数调用
        print('[生产者] 消费者返回:{0}'.format(r))
        print('-------------------------------------------------')
    c.close()


c = consumer()  # 构造一个生成器
produce(c)

'''运行结果为:
[生产者] 正在生产:1
[消费者] 正在消费:1
[生产者] 消费者返回:200 人民币
-------------------------------------------------
[生产者] 正在生产:2
[消费者] 正在消费:2
[生产者] 消费者返回:200 人民币
-------------------------------------------------
[生产者] 正在生产:3
[消费者] 正在消费:3
[生产者] 消费者返回:200 人民币
-------------------------------------------------
[生产者] 正在生产:4
[消费者] 正在消费:4
[生产者] 消费者返回:200 人民币
-------------------------------------------------
[生产者] 正在生产:5
[消费者] 正在消费:5
[生产者] 消费者返回:200 人民币
-------------------------------------------------
'''

解释分析:

第一步:在produce(c)函数中,调用了c.send(None)启动了生成器,这相当于是调用consumer(),但是如果consumer是一个普通函数而不是生成器,就要等到consumer执行完了,主动权才会重新回到producer手里。但就是因为consumer是生成器,所以第一次遇到yield暂停;接着执行produce()中接下来的代码,从运行结果看,确实打印出了[生产者] 正在生产 1 ,当程序运行至c.send(n)时,再次调用生成器并且通过yield传递了参数(n = 1),这个时候,进入consumer()函数先前在yield停下的地方,继续向后执行,所以打印出[消费者] 正在消费 1。

第二步:[消费者] 正在消费 1 这句话被打印出来之后,接下consumer()函数中此时 r 被赋值为’200 人民币’,接着consumer()函数里面的第一次循环结束,进入第二次循环,又遇到yield, 所以consumer()函数又暂停并且返回变量 r 的值,consumer()函数暂停,此时程序又进入produce(c)函数中接着执行。

第三步:由于先前produce(c)函数接着第一次循环中c.send(n)处相当于是调用消费者consumer(),跳入到了consumer()里面去执行,现在consumer暂停,producer重新我有主动权,故而继续往下执行打印出[生产者] 消费者返回: 200 人民币,然后producer的第一次循环结束,并进行第二次循环,打印出[生产者] 正在生产 1,然后,又调用c.send(n) 又调用消费者consumer,将控制权交给consumer,如此循环回到第一步!

(2)实例二:

def average():
    total = 0.0
    avg = None
    count = 0
    while True:
        num = yield avg
        total += num
        count += 1
        avg = total/count


# 定义一个函数,通过这个函数向average函数发送数值
def sender(generator):
    generator.send(None)
    print(generator.send(10))
    print(generator.send(20))
    print(generator.send(30))
    print(generator.send(40))


g = average()
sender(g)

'''运行结果为:
None
10.0
15.0
20.0
25.0
'''

三、协程的状态查看
协程有四种状态,分别是

GEN_CREATED:等待执行,即还没有进入协程

GEN_RUNNING:解释器执行(这个只有在使用多线程时才能查看到他的状态,而协程是单线程的)

GEN_SUSPENDED:在yield表达式处暂停(协程在暂停等待的时候的状态)

GEN_CLOSED:执行结束(协程执行结束了之后的状态)

四、yield实现协程的不足之处

(1)协程函数的返回值不是特别方便获取,只能够通过出发StopIteration异常,然后通过该异常的value属性获取;

(2)Python的生成器是协程coroutine的一种形式,但它的局限性在于只能向它的直接调用者每次yield一个值。这意味着那些包含yield的代码不能像其他代码那样被分离出来放到一个单独的函数中。这也正是yield from要解决的。

全文总结:

从某些角度来理解,协程其实就是一个可以暂停执行的函数,并且可以恢复继续执行。那么yield已经可以暂停执行了,如果在暂停后有办法把一些 value 发回到暂停执行的函数中,那么 Python 就有了『协程』。于是在PEP 342中,添加了 “把东西发送到已经暂停的生成器中” 的方法,这个方法就是send()。

相关文章

  • 协程

    gevent方法实现多任务(协程) 了解yield和grenlet yield方法创建协程 greenlet方法创建协程

  • Tornado入门(三)【协程】

    协程 在Tornado中,协程是推荐使用的异步方式。协程使用yield关键字暂停或者恢复执行,而不是回调链的方式。...

  • 2018-12-16 协程

    协程 又叫微线程,纤程python 对协程的实现是通过generator实现的 生成器-含有yield 有函数-生...

  • python协程(2):使用yield关键字实现协程

    一、什么是协程(coroutinr) 在学习写成之前、首先要明确一些概念,比如子程序、函数、并发、异步、多线程等等...

  • python协程2:yield from 从入门到精通

    上一篇python协程1:yield的使用介绍了: 生成器作为协程使用时的行为和状态 使用装饰器预激协程 调用方如...

  • Python控制流程-协程(1)

    句法上看, 协程与生成器类似, 都是定义体中包含yield关键字的函数。 具体的协程的使用: 1.在协程中yiel...

  • Python协程深入理解

    从语法上来看,协程和生成器类似,都是定义体中包含yield关键字的函数。yield在协程中的用法:在协程中yiel...

  • Python协程深入理解

    从语法上来看,协程和生成器类似,都是定义体中包含yield关键字的函数。yield在协程中的用法: 在协程中yie...

  • python拾遗5 - 协程

    在前面我们已经提到,在 python2 中使用 yield 实现协程,但是没有提到 python3 (3.7+)是...

  • 协程

    作为协程的生成器 协程使用生成器函数定义 如果协程只需从客户接收数据,那么yield右边没有关键字,产出为隐式指定...

网友评论

      本文标题:python协程(2):使用yield关键字实现协程

      本文链接:https://www.haomeiwen.com/subject/ahgakrtx.html