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


在这里还是要推荐下我自己建的Python开发学习群:725479218,群里都是学Python开发的,如果你正在学习Python ,小编欢迎你加入,大家都是软件开发党,不定期分享干货(只有Python软件开发相关的),包括我自己整理的一份2018最新的Python进阶资料和高级开发教程,欢迎进阶中和进想深入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",iurllib2.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",iurllib2.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)

作者:IT未来家
链接:https://www.jianshu.com/p/46cfc324c354
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Python协程(真才实学,想学的进来)相关推荐

  1. 我把 Python 协程的本质扒得干干净净!

    本文章信息量较大,从 IO 多路复用,到生成器的使用,再到 async.await 背后的实现原理,深入浅出,剖析得非常透彻,非常硬核! 这两天因为一点个人原因写了点好久没碰的 Python ,其中涉 ...

  2. 我把 Python 协程的本质扒得干干净净

    本文章信息量较大,从 IO 多路复用,到生成器的使用,再到 async.await 背后的实现原理,深入浅出,剖析得非常透彻,非常硬核!喜欢本文点赞支持,欢迎收藏学习,文末提供技术交流群. 这两天因为 ...

  3. python 协程_Python 协程与 Go 协程的区别(一)

    ? "Python猫" ,一个值得加星标的公众号 花下猫语:年关将近,不知各位过得怎样?我最近有些忙,收获也挺多,以后有机会分享下.吃饭时间,追了两部剧<了不起的麦瑟尔夫人& ...

  4. python协程实时输出_python协程

    不知道你有没有被问到过有没有使用过的python协程? 协程是什么? 协程是一种用户态轻量级,是实现并发编程的一种方式.说到并发,就能想到了多线程 / 多进程模型,是解决并发问题的经典模型之一. 但是 ...

  5. python中协程与函数的区别_深入浅析python 协程与go协程的区别

    进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...

  6. python 协程可以嵌套协程吗_Python线程、协程探究(2)——揭开协程的神秘面纱...

    一.上集回顾 在上一篇中我们主要研究了python的多线程困境,发现多核情况下由于GIL的存在,python的多线程程序无法发挥多线程该有的并行威力.在文章的结尾,我们提出如下需求: 既然python ...

  7. 简单聊聊Python协程

    往期好文推荐 学习Python不需要程基础? 0基础不用怕,从0到1轻松教你入门Python python系统学习流线图,教你一步一步学会python 成为一名做大数据开发的女程序员,并不是二狗进入大 ...

  8. python协程详解_彻底搞懂python协程-第一篇(关键词1-4)

    任何复杂的概念或系统都不是凭空出现的,我们完全可以找到它的演化历程,寻根究底终会发现,其都是在一系列并不那么复杂的简单组件上发展演化而来! by 落花僧 本文通过一系列关键概念,逐步递进理解协程. 0 ...

  9. python协程详解

    目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...

  10. python gevent async_谈谈Python协程技术的演进

    原标题:谈谈Python协程技术的演进 Coding Crush Python开发工程师 主要负责岂安科技业务风险情报系统redq. 引言 1.1. 存储器山 存储器山是 Randal Bryant ...

最新文章

  1. Python使用matplotlib可视化发散型点图、发散型点图可以同时处理负值和正值、并按照大小排序区分数据、为发散型点图添加数值标签(Diverging Dot Plot )
  2. 这样的阅读工具,人手一个不过分吧?
  3. android库项目管理,一个android工程代码多个差异化项目管理方法探讨
  4. linux 命令集锦
  5. http://jackielieu.blog.51cto.com/5586910/1161944
  6. Java 动态加载class 并反射调用方法
  7. Linq to xml 示例分析
  8. bzoj 3609: [Heoi2014]人人尽说江南好(博弈)
  9. 在 Mac OS X 下启用超级帐户(root)
  10. vue项目打包,生成dist文件夹,如何修改文件夹的名字
  11. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_01-vuejs研究-vuejs介绍
  12. Windows Server 2012 R2 打印服务器的设置与管理-深博-专题视频课程
  13. 大一新生c语言实验报告总结,实验报告总结
  14. 计算机的k代表什么意思,电脑CPU后缀K、U、HQ、M分别代表什么你清楚吗?
  15. 基于docker的redis4.0单机集群搭建
  16. html ios 视频播放不了,html中的video标签在ios微信中无法播放的解决方法之一
  17. 微课登陆显示服务器繁忙,老师为什么教别人孩子容易,教自家孩子却这么难?| 公益微课...
  18. 水文预报中的确定性系数如何计算确定
  19. 如何重新启动Windows的Explorer.exe(以及任务栏和“开始”菜单)
  20. 各种磁盘阵列模式(各种raid)之间的区别

热门文章

  1. 233. Number of Digit One
  2. 面试之 listview优化
  3. MATLAB优化问题
  4. 2019ICPC(银川) - Largest Common Submatrix(单调栈)
  5. UVa1600 PatrolRobot 巡逻机器人(bfs)
  6. VIM-多文件-多窗口
  7. 逆向工程核心原理读书笔记-API钩取之计算器显示中文数字
  8. 关于寻路算法的一些思考(3):A*算法的实现
  9. VC获取父进程PID
  10. 5分钟了解CDN 加速原理 | +新书推荐