Python - 协程 - Gevent - yield

Python在并发处理上不仅提供了多进程和多线程的处理,还包括了协程。

协程很类似于Javascript单线程下异步处理的概念,协程同样是单线程的,之所以能够进行并发是因为通过某种方式保存了执行栈的上下文,在一定条件下将执行权交由其他栈,在一定条件下又通过执行栈上下文恢复栈。


1. yield

与之一概念最为类似的是Python中的yield

class DemoTwo:
    @staticmethod
    def run():
        def gen(max):
            n = 0
            while n < max:
                yield n
                n += 1

        my_numbers = gen(10)
        for number in my_numbers:
            print(number)


带有yield的函数,已经不是一个普通的函数,这样的函数会返回一个“生成器”,这个生成器类似于迭代器(所以它可以被for使用),这个生成器也就是上文中的my_numbers有一个next()方法,用于取出下一个数。

表面上,我们可以看做my_numerbs保存了0-9,通过yield我们将这些数保存下来了,在我们进行迭代的时候他才会取出这些数。

实际上,这是一种错觉,如果只是单纯地保存执行结果,那yield看似并没有我们所想的强大。

我们来作一个实验来看看他到底是怎么样的:

class DemoTwo:
    @staticmethod
    def run():
        def gen(max):
            n = 0
            while n < max:
                print('start',n)
                yield n
                print('end',n)
                n += 1

        my_numbers = gen(10)
        my_numbers.__next__()
        my_numbers.__next__()


我们可以看到输出 (DemoTwo.run())

start 0
end 0
start 1


也许到了现在我们可以理解yield了。

yield相当于一个中断,将执行权交给了其他函数,同时将自己的栈上下文保存到了生成器中。

而在调用函数内next()方法则相当于还原了栈上下文并继续执行,直到下一个yield,又发生了一次中断。

再来看下面一段代码:

class DemoTwo:
    @staticmethod
    def run():
        def gen(max):
            n = 0
            while n < max:
                print('start',n)
                a = yield n
                print(a)
                print('end',n)
                n += 1

        my_numbers = gen(10)
        my_numbers.__next__()
        my_numbers.send('test')


yield并不仅仅可以传递一个参数,也可以接受一个参数,通过send的方法,我们可以灵活地与yield函数进行交互。

也许next可以看作next(None) (在python3中已经没有next(),取而代之的是__next__(),当然send已经足够了)


yield是一个不完全的协程实现,那么来总结一下:

1. 协程是一种并发的能力

2. 协程是单个线程内的表现

3. 协程通过程序保存上下文、执行等行为


和线程对比:

1. 协程没有线程切换开销

2. 单线程没有锁机制


2. gevent

gevent是一个python的协程模块(支持python2&3, windows & linux,windows需要通过whl方式安装)
gevent的初衷与nodejs相似,当运行IO密集型操作的时候,使用协程的方式挂起任务,当该IO操作完成时再进行调回,gevent则为我们自动切换协程任务,保证总能有任务进行,而不是等待IO。
这些任务实际上是greenlet,greenlet可以保存栈上下文(函数执行上下文),但需要手动进行切换。

每一个greenlet也拥有一些状态
started -表明已经启动 (boolean)
ready -表明已经停止(boolean)
successful -表明已经执行成功并且没有异常(boolean)
value -greenlet函数的返回值
exception -如果有异常,则是异常对象


我们已经了解到gevent可以基于greentlet进行协程运作,但一些系统包并没有携带协程功能,那由如果进行协程操作呢:
from gevent import monkey
monkey.patch_socket()


这个猴子补丁会修改socket/ssl/threading等模块,将阻塞式变为协程式

模板:
import gevent
from gevent.event import Event

'''
Illustrates the use of events
'''

evt = Event()

def setter():
    '''After 3 seconds, wake all threads waiting on the value of evt'''
    print('A: Hey wait for me, I have to do something')
    gevent.sleep(3)
    print("Ok, I'm done")
    evt.set()

def waiter():
    '''After 3 seconds the get call will unblock'''
    print("I'll wait for you")
    evt.wait()  # blocking
    print("It's about time")

def main():
    gevent.joinall([
        gevent.spawn(setter),
        gevent.spawn(waiter),
        gevent.spawn(waiter),
        gevent.spawn(waiter),
        gevent.spawn(waiter),
        gevent.spawn(waiter)
    ])

if __name__ == '__main__': main()



正如yield函数一样,greenlet也可以进行通讯,不同的greenlet之间可以通过Event、AsyncResult、Queue的 set/get/wait进行通讯,可以想象成yield.

你可能感兴趣的:(Python)