Python-协程

  协程(coroutine),又称微线程,纤程,是一种用户级别的轻量级线程,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存寄存器上下文和栈。因此协程能保留上一次调用时的状态,每次过程重入时,就相当于上一次调用的状态。在并发编程中,协程与县城类似,每个协程表示一个执行单元,有自己本地的数据库,与其他协程共享全局数据和其他资源。

优点:

  1.无需线程上下文切换的开销

  2.无需原子操作锁定及同步的开销

  3.方便切换控制流,简化编程模型

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

缺点:

  1.无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

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

听过第三方库gevent对协程的支持,本质上是greenlet在实现切换工作。

greenlet工作流程:

  访问网络的IO操作时,出现阻塞,greenlet就显式的切换到另一端没有被阻塞的代码段执行,直到先前的阻塞状况小时候,再自动切换回原来的代码段进行处理。

  由于IO操作的耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO,这就是携程比多线程高效的原因。由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的标准库,将一些常见的阻塞。如socket、select等地方实现协程跳转,这一过程在启动时通过monkey path完成。

gevent:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'Fade Zhao'


from gevent import monkey;
monkey.patch_all()
import gevent
import urllib.request
def run_task(url):
    print('Visit --> %s' % url)
    try:
        response =  urllib.request.urlopen(url)
        data = response.read()
        print('%d bytes received from %s.' % (len(data), url))
    except Exception as e:
        print(e)
if __name__=='__main__':
    urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/']
    greenlets = [gevent.spawn(run_task, url) for url in urls  ]
    gevent.joinall(greenlets)


'''
Visit --> https://github.com/
Visit --> https://www.python.org/
Visit --> http://www.cnblogs.com/
46181 bytes received from http://www.cnblogs.com/.
51474 bytes received from https://github.com/.
48879 bytes received from https://www.python.org/.
'''
以上程序使用了spawn和joinall函数
spawn:
    用来创建协程
joinall:
    用来批量添加并运行协程
从结果来看,三个协程是并发的,而且结束顺序不同,但其实只有一个线程。
  gevent也有对池的支持。当拥有动态数量的greenlet需要进行并发管理(限制并发数)时,就可以使用池,
这在处理大量网络IO是非常重要的。
from gevent import monkey
monkey.patch_all()
import urllib.request
from gevent.pool import Pool
def run_task(url):
    print('Visit --> %s' % url)
    try:
        response = urllib.request.urlopen(url)
        data = response.read()
        print('%d bytes received from %s.' % (len(data), url))
    except Exception as e:
        print(e)
    return 'url:%s --->finish'% url
if __name__=='__main__':
    pool = Pool(2)
    urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/']
    results = pool.map(run_task,urls)
    print(results)

'''
>>>
    Visit --> https://github.com/
    Visit --> https://www.python.org/
    48879 bytes received from https://www.python.org/.
    Visit --> http://www.cnblogs.com/
    46184 bytes received from http://www.cnblogs.com/.
    51474 bytes received from https://github.com/.
    ['url:https://github.com/ --->finish', 'url:https://www.python.org/ --->finish', 'url:http://www.cnblogs.com/ --->finish']
'''
    可以看出,Pool对象确实对协程的并发数量进行了管理,先访问了前两个网址,当其中一个任务完成时,才会执行第三个