python协程

协程,又称微线程,是用户级的轻量级线程。协程拥有自己的寄存器上下文和栈,调度切换时,将寄存器上下文保存在其他地方,切回来恢复。因此,协程能保留上一次调用的状态。

  • 在并发编程中,协程与线程类似,每个协程有自己的本地数据,与其他协程共享全局数据和其他资源
  • 协程需要用户自己编写调度逻辑,对CPU来说,协程其实是单线程,CPU不需要考虑怎样调度。

python通过yield提供了对协程的基本支持,但不完全,而使用第三方gevent库是更好的选择。gevent是基于协程的python网络函数库。

  • 使用greenlet在libev事件循环顶部提供了一个高级别并发性的api,主要特点:

    • 基于libev的快速事件循环,linux上是epoll机制
    • 基于greenlet的轻量级执行单元
    • API复用了python标准库里的内容
    • 支持SSL的协作式sockets
    • 可通过线程池或c-ares实现DNS查询
  • 通过monkey patching功能使得第三方模块变成协作式。

    • gevent对协程的支持,本质上是greenlet在实现切换工作。greenlet工作流程如下:

      • 假如进行访问网络IO操作时,出现阻塞,greenlet就显示切换到另一段没有被阻塞的代码段执行,直到原先的阻塞状况消失,再自动切回原来的代码段继续执行。串行方式

      • 有了gevent为我们自动切换协程,就保证greenlet在运行,而不是等待IO。这就是协程比一般多线程高效的原因

      • 由于切换是在IO操作时自动完成,所以gevent需要修改python自带的一些标准库,将一些常见的阻塞,如socket、select等地方实现跳转,这一过程启动通过monkey patch完成

#!coding:utf-8
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2

def run_task(url):
    print 'Visit --> %s' %url
    try:
        response = urllib2.urlopen(url)
        data = response.read()
        print '%d bytes received from %s.' % (len(data),url)
    except Exception,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)
C:\Python27\python.exe F:/python_scrapy/python_study/coroutine_greenlet.py
Visit --> https://github.com/
Visit --> https://www.python.org/
Visit --> http://www.cnblogs.com/
48814 bytes received from https://www.python.org/.
59963 bytes received from https://github.com/.
46880 bytes received from http://www.cnblogs.com/.

Process finished with exit code 0

  • 以上程序主要用了gevent中的spawn 方法和joinall 方法,spawn方法可以看做是用来形成协程,joinall 方法就是添加这些协程任务,并且启动运行。三个网络操作时并发执行的,而且结束顺序不同,但其实只
    有一个线程
    gevent 中也提供了对池的支持,对greenlet进行并发管理(限制并发数),就可以使用池,这在处理大量的网络和IO操作时时非常必要的,程序改写如下:
#!coding:utf-8
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2
from gevent.pool import Pool

def run_task(url):
    print 'Visit --> %s' %url
    try:
        response = urllib2.urlopen(url)
        data = response.read()
        print '%d bytes received from %s.' % (len(data),url)
    except Exception,e:
        print e
    return 'url:%s ---》finish'%url

if __name__=='__main__':
    urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/']
    pool = Pool(2)
    results = pool.map(run_task, urls)
    print results

  • 运行结果:
C:\Python27\python.exe F:/python_scrapy/python_study/coroutine_greenlet.py
Visit --> https://github.com/
Visit --> https://www.python.org/
48814 bytes received from https://www.python.org/.
Visit --> http://www.cnblogs.com/
59855 bytes received from https://github.com/.
46891 bytes received from http://www.cnblogs.com/.
['url:https://github.com/ ---\xe3\x80\x8bfinish', 'url:https://www.python.org/ ---\xe3\x80\x8bfinish', 'url:http://www.cnblogs.com/ ---\xe3\x80\x8bfinish']

Process finished with exit code 0

  • Pool对象确实对协程的并发数量进行管理,先访问了前两个网址,当其中一个完成时,才会执行第三个
posted @ 2018-08-01 10:52  大大的大笨熊  阅读(244)  评论(0编辑  收藏  举报