协程

1、协程的理解

协程,又称微线程,纤程。英文名Coroutine,是一种用户态的轻量级线程。

注意:
  1. python的线程属于内核级别的,即由操作系统控制调度(如单线程一旦遇到io就被迫交出cpu执行权限,切换其他线程运行)
  2. 单线程内开启协程,一旦遇到io,从应用程序级别(而非操作系统)控制切换
协程优点:
  1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  2. 单线程内就可以实现并发的效果,最大限度地利用cpu
协程缺点:
  1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
  2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方(线程调度时候寄存器上下文及栈等保存在内存中),在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。

所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

def A():
    print '1'
    print '2'
    print '3'
 
def B():
    print 'x'
    print 'y'
    print 'z'

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:

1
2
x
y
3
z

但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行。

2、yield的运用

从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数。可是,在协程中, yield 通常出现在表达式的右边(例如, datum = yield),可以产出值,也可以不产出 —— 如果 yield 关键字后面没有表达式,那么生成器产出 None。

协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是next(…) 函数。

==yield 关键字甚至还可以不接收或传出数据。不管数据如何流动, yield 都是一种流程控制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激活其他的协程==。

import random
def create_generator_function(n):
    i = 0
    while i < n:
        b = yield i
        print("It's {0}".format(b))
        i = i + 1
obj = create_generator_function(5)
obj1 = obj.next()
x = 0
while x<5:
    try:
        obj1 = obj.send(random.random()) 
    except:
        pass
    x += 1

我们先给协程一个标准定义,即符合什么条件就能称之为协程:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 一个协程遇到IO操作自动切换到其它协程

基于上面这4点定义,我们刚才用yield实现的程并不能算是合格的线程,不满足最后一条。

 如何实现单线程下并发效果:遇到IO操作就切换,协程之所以能处理大并发,就是由于挤掉了IO操作,使得CPU一直运行。

关键在于切换出来后,什么时候再切换回去??需要程序自动监测IO操作,IO操作结束就切换回去。

以上我们是通过yeild实现的协程的功能,yield能实现协程,不过实现过程不易于理解,greenlet是在这方面做了改进。

3、Greenlet

Greenlet是python的一个C扩展,来源于Stackless python,旨在提供可自行调度的‘微线程’, 即协程。generator实现的协程在yield value时只能将value返回给调用者(caller)。 而在greenlet中,target.switch(value)可以切换到指定的协程(target), 然后yield value。greenlet用switch来表示协程的切换,从一个协程切换到另一个协程需要显式指定。是一种手动切换

4、gevent

gevent是第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。其基本思想是:

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成。

Gevent 是一个基于 greenlet 的 Python 的并发框架,以微线程 greenlet 为核心,使用了 epoll 事件监听机制以及诸多其他优化而变得高效。

于 greenlet、eventlet 相比,性能略低,但是它封装的 API 非常完善,最赞的是提供了一个 monkey 类,可以将现有基于 Python 线程直接转化为 greenlet,相当于 proxy 了一下(打了 patch)。

功能包括:

gevent  eventlet 启发,但具有更一致的 API,更简单的实现和更好的性能。阅读为什么其他人使用 gevent 并查看基于 gevent 的开源项目列表。

posted @ 2019-04-23 17:49  Einewhaw  阅读(1637)  评论(0编辑  收藏  举报