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.5协程使用可以移步:Python3.5协程学习研究
为了测试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)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
运行结果:
test_1 3
test_1 4
test_1 0
test_1 1
test_1 2
test_2 3
test_2 0
test_2 2
test_2 4
test_2 1
说明:从运行结果可以看到,跟gevent达到的效果一样,也是在遇到IO操作时进行切换(所以先输出test_1,等test_1输出完再输出test_2)。但此处我有一点不明,test_1的输出为什么不是按照顺序执行的呢?可以对比gevent的输出结果(希望大神能解答一下)。
asyncio说明
@asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。
test()会首先打印出test_1,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。
把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。
asynico/await
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
- 把@asyncio.coroutine替换为async;
- 把yield from替换为await。
Usage
例子(python3.5以后版本使用):
import asyncio
async def test(i):
print("test_1",i)
await asyncio.sleep(1)
print("test_2",i)
loop=asyncio.get_event_loop()
tasks=[test(i) for i in range(5)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
运行结果与之前一致。
说明:与前一节相比,这里只是把yield from换成了await,@asyncio.coroutine换成了async,其余不变。
gevent
同python2.x用法一样。
协程VS多线程
如果通过以上介绍,你已经明白多线程与协程的不同之处,那么我想测试也就没有必要了。因为当线程越来越多时,多线程主要的开销花费在线程切换上,而协程是在一个线程内切换的,因此开销小很多,这也许就是两者性能的根本差异之处吧。(个人观点)
异步爬虫
也许关心协程的朋友,大部分是用其写爬虫(因为协程能很好的解决IO阻塞问题),然而我发现常用的urllib、requests无法与asyncio结合使用,可能是因为爬虫模块本身是同步的(也可能是我没找到用法)。那么对于异步爬虫的需求,又该怎么使用协程呢?或者说怎么编写异步爬虫?
给出几个我所了解的方案:
- grequests (requests模块的异步化)
- 爬虫模块+gevent(比较推荐这个)
- aiohttp (这个貌似资料不多,目前我也不太会用)
- asyncio内置爬虫功能 (这个也比较难用)
协程池
作用:控制协程数量
from bs4 import BeautifulSoup
import requests
import gevent
from gevent import monkey, pool
monkey.patch_all()
jobs = []
links = []
p = pool.Pool(10)
urls = [
'http://www.google.com',
# ... another 100 urls
]
def get_links(url):
r = requests.get(url)
if r.status_code == 200:
soup = BeautifulSoup(r.text)
links + soup.find_all('a')
for url in urls:
jobs.append(p.spawn(get_links, url))
gevent.joinall(jobs)
Python协程(真才实学,想学的进来)的更多相关文章
- day-5 python协程与I/O编程深入浅出
基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1. 什么是协程(以下内容来自维基百 ...
- 关于python协程中aiorwlock 使用问题
最近工作中多个项目都开始用asyncio aiohttp aiomysql aioredis ,其实也是更好的用python的协程,但是使用的过程中也是遇到了很多问题,最近遇到的就是 关于aiorwl ...
- Python协程与Go协程的区别二
写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...
- Python 协程总结
Python 协程总结 理解 协程,又称为微线程,看上去像是子程序,但是它和子程序又不太一样,它在执行的过程中,可以在中断当前的子程序后去执行别的子程序,再返回来执行之前的子程序,但是它的相关信息还是 ...
- [转载] Python协程从零开始到放弃
Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC 2017-10-09 3,973 Author: lightless@Meili-inc Date: 2017100 ...
- python协程详解
目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...
- python 协程与go协程的区别
进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...
- python协程详解,gevent asyncio
python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...
- Python核心技术与实战——十六|Python协程
我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...
- 5分钟完全掌握Python协程
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 1. 协程相关的概念 1.1 进程和线程 进程(Process)是应用程序启动的实例,拥有代码.数据 ...
随机推荐
- JVM虚拟机学习一:垃圾回收算法总结
1.java虚拟机中涉及到的数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型. 基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某 ...
- springboot 学习之路 1(简单入门)
目录:[持续更新.....] spring 部分常用注解 spring boot 学习之路1(简单入门) spring boot 学习之路2(注解介绍) spring boot 学习之路3( 集成my ...
- JAVA基础库的使用点滴
Idea中双击SHIFT可以搜索[Eclipse中也有],这个很重要,可以找到当前的项目中可以引擎的已有的类,不要再次自己发明轮子 各种Util先在基础库和开源库中找 Base64编码 guava:c ...
- OneAPM 重磅登陆 CTDC 2018展示“ AIOps 双雄”创新实力
9月7日-8日,以“AI 智享未来”为主题的2018第二届 CTDC 首席技术官领袖峰会在乌镇盛大召开,大会由 CTOA 首席技术官领袖联盟.ITShare 主办,大会邀请了国内外顶级互联网.在线教育 ...
- web测试笔记
WEB兼容性测试 一.客户端兼容性 1.浏览器的兼容性测试 a.内核角度 Tridnt内核:代表作IE.腾讯.遨游.世界之窗等 Gecko内核:代表作Firefox webkit内核:代表作Safar ...
- 为什么会出现Notice: Undefined index: submit in D:\xampp\htdocs\test.php on line 19
事例如下": <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:/ ...
- 一个好看的php验证码源码
<?php $w = 80; //设置图片宽和高 $h = 26; $str = Array(); //用来存储随机码 $string = "ABCDEFGHIJKLMNOPQ ...
- jQuery -- 光阴似箭(五):AJAX 方法
jQuery -- 知识点回顾篇(五):AJAX 方法 1. $.ajax 方法:用于执行 AJAX(异步 HTTP)请求. <!DOCTYPE html> <html> &l ...
- LeetCode算法题-Add Binary(Java实现)
这是悦乐书的第157次更新,第159篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第16题(顺位题号是67).给定两个二进制字符串,返回它们的总和(也是二进制字符串).输 ...
- 谱聚类算法(Spectral Clustering)优化与扩展
谱聚类(Spectral Clustering, SC)在前面的博文中已经详述,是一种基于图论的聚类方法,简单形象且理论基础充分,在社交网络中广泛应用.本文将讲述进一步扩展其应用场景:首先是User- ...