• Python 中的协程 (1) coroutine


    1 什么是协程 coroutine

      协程: 又称微线程,纤程。在一个线程内执行。 子程序: 函数,调用是通过栈来实现的。一个调用一个返回。 多线程: 避免程序顺序执行的方式之一是多线程。GIL锁只能一个点一个线程,对于io操作会有性能提升,但是依然 有线程的管理和切换,同步的开销等等 协程与一般函数的不同: 协程内部可以中断并切换,且保存当前执行状态。 协程和多线程对比的优势: 协程具有极高的执行效率,且在用户态进行,线程数量越多,性能就越好。 多进程 + 协程: 既充分利用多核,又充分发挥协程的高效率。

       协程是针对单个cpu的,因为协程运行在单线程内,可以使用协程实现类似多并发的任务,并且在单cpu时,效率一般要比协程高。

      在使用协程时,不由cpu来分配调度时间,时间由协程自定义。如协程遇到I/O 操作,就主动让出cpu的使用权,保存当前状态。然后等到I/O 完成,再申请使用cpu。即让编程者控制各个任务的运行顺序,从而最大限度使用cpu。

    2 协程的实现

      使用yield 关键字

    1. yield 存在的函数是一个生成器,当调用这个函数时,函数不会立即执行,而是返回一个生成器对象。使用生成器对象时,代码块才会执行。

    2. yield 有两个关键作用

      1. 返回一个值

      2. 接收调用方传入的值,且不影响返回值

    3 例子

    def consume():
        r = ''
        while True:
            n = yield r        # 断点
            if not n:
                return
            print('消费者 正在消费: {}'.format(n))
            r = '200 RMB'
            
    def produce(c):
        c.send(None)    # 启动生成器
        n = 0
        while n < 5:
            n += 1
            print('生产者 正在生产: {}'.format{n})
            r = c.send(n)
            print('[生产者] 消费者返回: {}'.format{r})
            print('------------')
        c.close()
    c = consume()
    produce(c)

    4 分析

      yield 是实现生成器的重要关键字。

      yiled 的三个方法及其作用, next(), send(),throw().

      yield的一般形式为:temp=yield 表达式(每次迭代要返回的值)(推荐使用:既可以返回迭代的值,也可以接受send进去的参数并使用)

    def my_generator(n):
        for i in range(n):
            temp = yield i
            print(f'我是{temp}')
    
    g=my_generator(5)
    
    print(next(g)) #输出0, 打印结果,我是None
    print(next(g)) #输出1
    g.send(100)    #temp的打印结果为100
    print(g.send(100))       #输出2
    print(next(g)) #输出3
    print(next(g)) #输出4

      

      特点:

    1. 延迟加载特性,生成器需要启动,如果用send启动,第一次需要传入None,否则报错

    2. 使用send()方法传进去的值,实际上就是yield表达式返回的值,没有传值则默认返回None

    3. 如果使用了send(value),传递进去的那个value会取代那个表达式的值,并且会将传递进去的那个值返回给yield表达式的结果temp,而send(value)的返回值就是原来的值

    4. 每次惰性返回一个值

    send 方法:
      send(value)是有返回值的,即迭代的那个值。
      send 的主要作用 当需要手动更改生成器里面的某一个值并且使用它,则send发送进去一个数据,然后保存到yield语句的返回值,以提供使用;send(value)的返回值就是那个本来应该被迭代出来的那个值。这样既可以保证我能够传入新的值,原来的值也不会弄丢。
    throw 方法:

      在生成器中抛出异常,并且这个throw函数会返回下一个要迭代的值或者是StopIteration。

      

    def my_generator():
        yield 'a'
        yield 'b'
        yield 'c'
        yield 'd'
    g=my_generator()
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(StopIteration))
    print(next(g))
    '''运行结果为:
    a
    b
    -------------------------
    StopIteration

    触发异常之后,返回StopIteration,同时迭代终止,而StopIteration作为被传入的yield值,会顺位进入下一次迭代,即把后置位的 ‘c' 覆盖掉。

    再看一个例子

    def my_generator():
        while True:
            try:
                yield 'a'
                yield 'b'
                yield 'c'
                yield 'd'
                yield 'e'
            except ValueError:
                print('触发“ValueError"了')
            except TypeError:
                print('触发“TypeError"了')
    
    g=my_generator()
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(ValueError))
    print('-------------------------')
    print(next(g))
    print(next(g))
    print('-------------------------')
    print(g.throw(TypeError))
    print('-------------------------')
    print(next(g))

    加入while 循环之后,循环会执行完成所有调用的 yield, throw时也会执行一个yield,而throw掉的异常被捕获了,所以就不会覆盖掉 ’a‘,a顺位进入下一次迭代,这就是a在 分割线之间的原因了。

    生成器的启动与 close

    生成器启动时,使用next可以直接启动并调用,但是使用 send() 第一次要传入 None,不然会报错。

    close() 表示关闭生成器,如果后续再调用此生成器,那么会抛出异常。

    生成器的终止- StopIteration

    在一个生成器中,如果没有return,则默认执行到函数完毕返回 StopIteration;

    如果遇到 return,则直接抛出 StopIteration,如果 return 后面有值,会一并返回。

    可以使用 except StopIteration as e 捕获异常,通过e.value 来获取值。

     

    5 协程的状态查看

    协程分别有四种状态,可以导入 inspect.getgeneratorstate() 模块来查看

      GEN_CREATED: 被创建,等待执行

      GEN_RUNNING: 解释器执行

      GEN_SUSPENDED: 在 yield 表达式处暂停

      GEN-CLOSED: 执行结束

    6 协程的个人理解

      从某些角度来说,协程其实就是一个可以暂停执行的函数,并且可以继续执行。那么 yield 已经可以暂停执行了,如果在暂停后有办法把一些 value 发送到暂停执行的函数中,那么这就是 Python 中的协程。

      不足之处:协程函数的返回值不是特别方便获取,比如 return 的返回值需要捕获异常as e,使用e.value来获得

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

     

  • 相关阅读:
    单链表的基本操作(查找,插入,删除)
    线性表的基本操作(插入,删除,查找)
    双人五子棋对战(需要EasyX图像库)
    2016ACM竞赛训练暑期课期末考试 a题
    百练_1664 放苹果
    百练_4120 硬币(DP)
    PAT_1046 划拳
    PAT_1026 程序运行时间
    学Android开发 这19个开发工具助你顺风顺水
    JAVA利用axis2发布webservice
  • 原文地址:https://www.cnblogs.com/wang-kai-1994/p/10375140.html
Copyright © 2020-2023  润新知