Python与协程从Python2—Python3

协程,又称微线程、纤程,英文名Coroutine;用一句话说明什么是线程的话:协程是一种用户态的轻量级线程。python

Python对于协程的支持在python2中还比较简单,可是也有可使用的第三方库,在python3中开始全面支持,也成为python3的一个核心功能,很值得学习。git

 

协程介绍

协程,又称微线程、纤程,英文名Coroutine;用一句话说明什么是线程的话:协程是一种用户态的轻量级线程。程序员

协程拥有本身的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其余地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。所以:协程能保留上一次调用时的状态(即全部局部状态的一个特定组合),每次过程重入时,就至关于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。github

协程的优势:编程

1)无需线程上下文切换的开销网络

2)无需原子操做锁定及同步的开销并发

3)方便切换控制流,简化编程模型异步

4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。因此很适合用于高并发处理。socket

协程的缺点:async

1)没法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程须要和进程配合才能运行在多CPU上

2)进行阻塞(Blocking)操做(如IO时)会阻塞掉整个程序

 

Python2中的协程

yield关键字

Python2对于协程的支持,是经过yield关键字实现的,下面示例代码是一个常见的生产者—消费者模型,代码示例以下:


def consumer():

    r = ''

    while True:

        n = yield r

        if not n:

            continue

        print('[CONSUMER] Consuming %s...' % n)

        r = '200 OK'

 

def produce(c):

    c.next()

    n = 0

    while n < 5:

        n = n + 1

        print('[PRODUCER] Producing %s...' % n)

        r = c.send(n)

        print('[PRODUCER] Consumer return: %s' % r)

    c.close()

 

if __name__ == '__main__':

    c = consumer()

    produce(c)

  


执行结果:

注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:

1)首先调用c.next()启动生成器;

2)而后,一旦生产了东西,经过c.send(n)切换到consumer执行;

3)consumer经过yield拿到消息,处理,又经过yield把结果传回;

4)produce拿到consumer处理的结果,继续生产下一条消息;

5)produce决定不生产了,经过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协做完成任务,因此称为“协程”,而非线程的抢占式多任务。

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,经过锁机制控制队列和等待,但一不当心就可能死锁。

若是改用协程,生产者生产消息后,直接经过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。

Python对协程的支持还很是有限,用在generator中的yield能够必定程度上实现协程。虽然支持不彻底,但已经能够发挥至关大的威力了。

 

gevent模块

Python经过yield提供了对协程的基本支持,可是不彻底。而第三方的gevent为Python提供了比较完善的协程支持。gevent是第三方库,经过greenlet实现协程,其基本思想是:

当一个greenlet遇到IO操做时,好比访问网络,就自动切换到其余的greenlet,等到IO操做完成,再在适当的时候切换回来继续执行。因为IO操做很是耗时,常常使程序处于等待状态,有了gevent为咱们自动切换协程,就保证总有greenlet在运行,而不是等待IO。因为切换是在IO操做时自动完成,因此gevent须要修改Python自带的一些标准库,这一过程在启动时经过monkey patch完成。

示例代码以下:


from gevent import monkey; monkey.patch_all()

import gevent

import urllib2

 

def f(url):

    print('GET: %s' % url)

    resp = urllib2.urlopen(url)

    data = resp.read()

    print('%d bytes received from %s.' % (len(data), url))

 

gevent.joinall([

        gevent.spawn(f, 'https://www.python.org/'),

        gevent.spawn(f, 'https://www.yahoo.com/'),

        gevent.spawn(f, 'https://github.com/'),

])

  


执行结果:

从执行结果能够看到,网站访问的顺序是自动切换的。

 

gevent优缺

使用gevent,能够得到极高的并发性能,但gevent只能在Unix/Linux下运行,在Windows下不保证正常安装和运行。Python创始人Gvanrossum历来不喜欢Gevent,而是更愿意另辟蹊径的实现asyncio(python3中的异步实现)。

1)Monkey-patching。中文「猴子补丁」,经常使用于对测试环境作一些hack。Gvanrossum说用它就是”patch-and-pray”,因为Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协做式运行。可是没法保证在复杂的生产环境中有哪些地方使用这些标准库会因为打了补丁而出现奇怪的问题,那么只能祈祷(pray)了。

2)其次,在Python之禅中明确说过:「Explicit is better than implicit.」,猴子补丁明显的背离了这个原则。

3)第三方库支持。得确保项目中用到其余用到的网络库也必须使用纯Python或者明确说明支持Gevent,并且就算有这样的第三方库,也须要担忧这个第三方库的代码质量和功能性。

4)Greenlet不支持Jython和IronPython,这样就没法把gevent设计成一个标准库了。

以前是没有选择,不少人选择了Gevent,而如今明确的有了更正统的、正确的选择:asyncio(下一节会介绍)。因此建议你们了解Gevent,拥抱asyncio。

另外,若是知道如今以及将来使用Gevent不会给项目形成困扰,那么用Gevent也是能够的。

 

Python3中的协程

Gvanrossum但愿在Python 3 实现一个原生的基于生成器的协程库,其中直接内置了对异步IO的支持,这就是asyncio,它在Python 3.4被引入到标准库。

下面将简单介绍asyncio的使用:

1)event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当知足事件发生的时候,调用相应的协程函数。

2)coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会当即执行函数,而是会返回一个协程对象。协程对象须要注册到事件循环,由事件循环调用。

3)task 任务:一个协程对象就是一个原生能够挂起的函数,任务则是对协程进一步封装,其中包含任务的各类状态。

4)future: 表明未来执行或没有执行的任务的结果。它和task上没有本质的区别

5)async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。

代码示例以下:


import asyncio

import time

 

now = lambda: time.time()

async def do_some_work(x):

    print('Waiting: {}s'.format(x))

 

    await asyncio.sleep(x)

    return 'Done after {}s'.format(x)

 

async def main():

    coroutine1 = do_some_work(1)

    coroutine2 = do_some_work(5)

    coroutine3 = do_some_work(3)

 

    tasks = [

        asyncio.ensure_future(coroutine1),

        asyncio.ensure_future(coroutine2),

        asyncio.ensure_future(coroutine3)

    ]

    done, pending = await asyncio.wait(tasks)

    for task in done:

        print('Task ret: ', task.result())

 

start = now()

 

loop = asyncio.get_event_loop()

task = asyncio.ensure_future(main())

try:

    loop.run_until_complete(task)

    print('TIME: ', now() - start)

except KeyboardInterrupt as e:

    print(asyncio.Task.all_tasks())

    print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())

    loop.stop()

    loop.run_forever()

finally:

    loop.close()

  


执行结果:

能够看到程序执行时间是以等待时间最长的为准。

 

使用async能够定义协程对象,使用await能够针对耗时的操做进行挂起,就像生成器里的yield同样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其余的协程也挂起或者执行完毕,再进行下一个协程的执行。耗时的操做通常是一些IO操做,例如网络请求,文件读取等。咱们使用asyncio.sleep函数来模拟IO操做。协程的目的也是让这些IO操做异步化。

 

Asyncio是python3中一个强大的内置库,上述只是简单的介绍了asyncio的用法有兴趣的话,很值得去学习一下!

相关文章
相关标签/搜索