专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
Python开发者  ·  自己编写作弊软件骗过大厂!00后拿4个顶级o ... ·  2 天前  
Python开发者  ·  程序员的“冰火两重天”:转型之路如何破局? ·  4 天前  
Python开发者  ·  突发!OpenAI 紧急上书,呼吁禁用 ... ·  5 天前  
Python中文社区  ·  稳住别慌!用这个量化策略控制回撤,收益逆天! ·  2 天前  
Python爱好者社区  ·  75k,直接封神! ·  3 天前  
51好读  ›  专栏  ›  Python开发者

Python 协程

Python开发者  · 公众号  · Python  · 2017-07-14 16:54

正文

(点击 上方蓝字 ,快速关注我们)


来源:nMask's Blog

thief.one/2017/02/20/Python%E5%8D%8F%E7%A8%8B/

如有好文章投稿,请点击 → 这里了解详情


真正有知识的人的成长过程,就像麦穗的成长过程:麦穗空的时候,麦子长得很快,麦穗骄傲地高高昂起,但是,麦穗成熟饱满时,它们开始谦虚,垂下麦芒。

——蒙田《蒙田随笔全集》


上篇《 Python 多线程鸡年不鸡肋 》论述了关于python多线程是否是鸡肋的问题,得到了一些网友的认可,当然也有一些不同意见,表示协程比多线程不知强多少,在协程面前多线程算是鸡肋。好吧,对此我也表示赞同,然而上篇我论述的观点不在于多线程与协程的比较,而是在于IO密集型程序中,多线程尚有用武之地。


对于协程,我表示其效率确非多线程能比,但本人对此了解并不深入,因此最近几日参考了一些资料,学习整理了一番,在此分享出来仅供大家参考,如有谬误请指正,多谢。申明:本文介绍的协程是入门级别,大神请绕道而行,谨防入坑。


文章思路:本文将先介绍协程的概念,然后分别介绍Python2.x与3.x下协程的用法,最终将协程与多线程做比较并介绍异步爬虫模块。


协程


概念


协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行。


优势


  • 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。

  • 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。


说明:协程可以处理IO密集型程序的效率问题,但是处理CPU密集型不是它的长处,如要充分发挥CPU利用率可以结合多进程+协程。


以上只是协程的一些概念,可能听起来比较抽象,那么我结合代码讲一讲吧。这里主要介绍协程在Python的应用,Python2对协程的支持比较有限,生成器的yield实现了一部分但不完全,gevent模块倒是有比较好的实现;Python3.4以后引入了asyncio模块,可以很好的使用协程。


Python2.x协程


python2.x协程应用:


  • yield

  • gevent


python2.x中支持协程的模块不多,gevent算是比较常用的,这里就简单介绍一下gevent的用法。


Gevent


gevent是第三方库,通过greenlet实现协程,其基本思想:

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


Install


pip install gevent


最新版貌似支持windows了,之前测试好像windows上运行不了……


Usage


首先来看一个简单的爬虫例子:


#! -*- coding:utf-8 -*-

import gevent

from gevent import monkey ; monkey . patch_all ()

import urllib2

def get_body ( i ) :

print "start" , i

urllib2 . urlopen ( "http://cn.bing.com" )

print "end" , i

tasks = [ gevent . spawn ( get_body , i ) for i in range ( 3 )]

gevent . joinall ( tasks )


运行结果:


start 0

start 1

start 2

end 2

end 0

end 1


说明:从结果上来看,执行get_body的顺序应该先是输出”start”,然后执行到urllib2时碰到IO堵塞,则会自动切换运行下一个程序(继续执行get_body输出start),直到urllib2返回结果,再执行end。也就是说,程序没有等待urllib2请求网站返回结果,而是直接先跳过了,等待执行完毕再回来获取返回值。值得一提的是,在此过程中,只有一个线程在执行,因此这与多线程的概念是不一样的。


换成多线程的代码看看:


import threading

import urllib2

def get_body ( i ) :

print "start" , i

urllib2 . urlopen ( "http://cn.bing.com" )

print "end" , i

for i in range ( 3 ) :

t = threading . Thread ( target = get_body , args = ( i ,))

t . start ()


运行结果:


start 0

start 1

start 2

end 1

end 2

end 0


说明:从结果来看,多线程与协程的效果一样,都是达到了IO阻塞时切换的功能。不同的是,多线程切换的是线程(线程间切换),协程切换的是上下文(可以理解为执行的函数)。而切换线程的开销明显是要大于切换上下文的开销,因此当线程越多,协程的效率就越比多线程的高。(猜想多进程的切换开销应该是最大的)


Gevent使用说明


  • monkey可以使一些阻塞的模块变得不阻塞,机制:遇到IO操作则自动切换,手动切换可以用gevent.sleep(0)(将爬虫代码换成这个,效果一样可以达到切换上下文)

  • gevent.spawn 启动协程,参数为函数名称,参数名称

  • gevent.joinall 停止协程


Python3.x协程


为了测试Python3.x下的协程应用,我在virtualenv下安装了python3.6的环境。

python3.x协程应用:


  • asynico + yield from(python3.4)

  • asynico + await(python3.5)

  • gevent


Python3.4以后引入了asyncio模块,可以很好的支持协程。


asynico


asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。asyncio的异步操作,需要在coroutine中通过yield from完成。


Usage


例子:(需在python3.4以后版本使用)


import asyncio

@ asyncio . coroutine

def test ( i ) :

print ( "test_1" , i )

r = yield from asyncio . sleep ( 1 )

print ( "test_2" , i )

loop = asyncio . get_event_loop ()

tasks = [ test ( i ) for i in range ( 5







请到「今天看啥」查看全文