Python gevent学习笔记
gevent是Python的一个用于网络IO的函数库,其中应用到了 coroutine(协同程序) 的思想。首先来了解下目前网络框架的几种基本的网络I/O模型:
阻塞式单线程:这是最基本的I/O模型,只有在处理完一个请求之后才会处理下一个请求。它的缺点是效能差,如果有请求阻塞住,会让服务无法继续接受请求。但是这种模型编写代码相对简单,在应对访问量不大的情况时是非常适合的。
阻塞式多线程:针对于单线程接受请求量有限的缺点,一个很自然的想法就是给每一个请求开一个线程去处理。这样做的好处是能够接受更多的请求,缺点是在线程产生到一定数量之后,进程之间需要大量进行切换上下文的操作,会占用CPU大量的时间,不过这样处理的话编写代码的难道稍高于单进程的情况。
非阻塞式事件驱动:为了解决多线程的问题,有一种做法是利用一个循环来检查是否有网络IO的事件发生,以便决定如何来进行处理(reactor设计模式)。这样的做的好处是进一步降低了CPU的资源消耗。缺点是这样做会让程序难以编写,因为请求接受后的处理过程由reactor来决定,使得程序的执行流程难以把握。当接受到一个请求后如果涉及到阻塞的操作,这个请求的处理就会停下来去接受另一个请求,程序执行的流程不会像线性程序那样直观。twisted框架就是应用这种IO模型的典型例子。
非阻塞式Coroutine:这个模式是为了解决事件驱动模型执行流程不直观的问题,它在本质上也是事件驱动的,加入了Coroutine的概念,我要学习的gevent就是应用这种IO模型的函数库。
接下来说说Coroutine(协程)这个概念,coroutine可以理解为一个轻量级的线程,为了解决了多线程上下文切换的损耗,提供了一个软件的协程切换。并且相对于事件驱动,能够将程序的执行过程由编写程序的人更好的控制。下面的图展现了协程的执行过程:

在了解了关于gevent的基本概念之后,接下来了就开始安装gevent。
|
1
2
3
|
apt-get install libevent-devapt-get install python-all-devpip install gevent |
现在基本的概念了解后,接下来就可以开始了解相关的代码了
-----------------------------------------------------------------------------
在上一篇里了解了gevent应用的IO模型概念之后,接下来开始真正了解gevent的使用。
Greenlet
在gevent里面最多应用到的就是greenlet,一个轻量级的协程实现。在任何时间点,只有一个greenlet处于运行状态。Greenlet与multiprocessing 和 threading这两个库提供的真正的并行结构的区别在于这两个库会真正的切换进程,POSIX线程是由操作系统来负责调度,并且它们是真正并行的。
同步和异步
应对并发的主要思路就是将一个大的任务分解成一个子任务的集合并且能够让它并行或者异步地执行,而不是一次执行一个或者同步执行。在两个子任务中的切换被称为上下文切换。
gevent里面的上下文切换是非常平滑的。在下面的例子程序中,我们可以看到两个上下文通过调用 gevent.sleep()来互相切换。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import geventdef foo(): print('Running in foo') gevent.sleep(0) print('Explicit context switch to foo again')def bar(): print('Explicit context to bar') gevent.sleep(0) print('Implicit context switch back to bar')gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar),]) |
这段程序的执行结果如下:
|
1
2
3
4
|
Running in fooExplicit context to barExplicit context switch to foo againImplicit context switch back to bar |
从这个执行结果可以看出这个程序的执行过程,在这里的两个函数是交替执行的。
gevent的真正威力是在处理网络和带有IO阻塞的功能时能够这些任务协调地运行。gevent来实现了这些具体的细节来保证在需要的时候greenlet上下文进行切换。在这里用一个例子来说明。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import timeimport geventfrom gevent import selectstart = time.time()tic = lambda: 'at %1.1f seconds' % (time.time() - start)def gr1(): # Busy waits for a second, but we don't want to stick around... print('Started Polling: ', tic()) select.select([], [], [], 2) print('Ended Polling: ', tic())def gr2(): # Busy waits for a second, but we don't want to stick around... print('Started Polling: ', tic()) select.select([], [], [], 2) print('Ended Polling: ', tic())def gr3(): print("Hey lets do some stuff while the greenlets poll, at", tic()) gevent.sleep(1)gevent.joinall([ gevent.spawn(gr1), gevent.spawn(gr2), gevent.spawn(gr3),]) |
在上面的例子里,select() 通常是一个阻塞的调用。
程序的执行结果如下:
|
1
2
3
4
5
|
Started Polling: at 0.0 secondsStarted Polling: at 0.0 secondsHey lets do some stuff while the greenlets poll, at at 0.0 secondsEnded Polling: at 2.0 secondsEnded Polling: at 2.0 seconds |
接下来一个例子中可以看到gevent是安排各个任务的执行的。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import geventimport randomdef task(pid): """ Some non-deterministic task """ gevent.sleep(random.randint(0,2)*0.001) print('Task', pid, 'done')def synchronous(): for i in range(1,10): task(i)def asynchronous(): threads = [gevent.spawn(task, i) for i in xrange(10)] gevent.joinall(threads)print('Synchronous:')synchronous()print('Asynchronous:')asynchronous() |
执行结果如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
root@master:~# python two.py Synchronous:('Task', 1, 'done')('Task', 2, 'done')('Task', 3, 'done')('Task', 4, 'done')('Task', 5, 'done')('Task', 6, 'done')('Task', 7, 'done')('Task', 8, 'done')('Task', 9, 'done')Asynchronous:('Task', 0, 'done')('Task', 9, 'done')('Task', 7, 'done')('Task', 3, 'done')('Task', 6, 'done')('Task', 5, 'done')('Task', 4, 'done')('Task', 1, 'done')('Task', 2, 'done')('Task', 8, 'done') |
在同步的情况下,任务是按顺序执行的,在执行各个任务的时候会阻塞主线程。
而gevent.spawn 的重要功能就是封装了greenlet里面的函数。初始化的greenlet放在了threads这个list里面,被传递给了 gevent.joinall 这个函数,它会阻塞当前的程序来执行所有的greenlet。
在异步执行的情况下,所有任务的执行顺序是完全随机的。每一个greenlet的都不会阻塞其他greenlet的执行。
在有时候需要异步地从服务器获取数据,gevent可以通过判断从服务器的数据载入情况来处理请求。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import gevent.monkeygevent.monkey.patch_socket()import geventimport urllib2import simplejson as jsondef fetch(pid): response = urllib2.urlopen('http://json-time.appspot.com/time.json') result = response.read() json_result = json.loads(result) datetime = json_result['datetime'] print 'Process ', pid, datetime return json_result['datetime']def synchronous(): for i in range(1,10): fetch(i)def asynchronous(): threads = [] for i in range(1,10): threads.append(gevent.spawn(fetch, i)) gevent.joinall(threads)print 'Synchronous:'synchronous()print 'Asynchronous:'asynchronous() |
确定性
就像之前说的,greenlet是确定的。给每个greenlet相同的配置和相同的输入,得到的输出是相同的。我们可以用python 的多进程池和gevent池来作比较。下面的例子可以说明这个特点:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import timedef echo(i): time.sleep(0.001) return i# Non Deterministic Process Poolfrom multiprocessing.pool import Poolp = Pool(10)run1 = [a for a in p.imap_unordered(echo, xrange(10))]run2 = [a for a in p.imap_unordered(echo, xrange(10))]run3 = [a for a in p.imap_unordered(echo, xrange(10))]run4 = [a for a in p.imap_unordered(echo, xrange(10))]print( run1 == run2 == run3 == run4 )# Deterministic Gevent Poolfrom gevent.pool import Poolp = Pool(10)run1 = [a for a in p.imap_unordered(echo, xrange(10))]run2 = [a for a in p.imap_unordered(echo, xrange(10))]run3 = [a for a in p.imap_unordered(echo, xrange(10))]run4 = [a for a in p.imap_unordered(echo, xrange(10))]print( run1 == run2 == run3 == run4 ) |
下面是执行结果:
|
1
2
|
FalseTrue |
从上面的例子可以看出,执行同一个函数,产生的greenlet是相同的,而产生的process是不同的。
在处理并发编程的时候会碰到一些问题,比如竞争资源的问题。最简单的情况,当有两个线程或进程访问同一资源并且修改这个资源的时候,就会引发资源竞争的问题。那么这个资源最终的值就会取决于那个线程或进程是最后执行的。这是个问题,总之,在处理全局的程序不确定行为的时候,需要尽量避免资源竞争的问题
最好的方法就是在任何时候尽量避免使用全局的状态。全局状态是经常会坑你的!
产生Greenlet
在gevent里面封装了一些初始化greenlet的方法,下面是几个最常用的例子:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import geventfrom gevent import Greenletdef foo(message, n): """ Each thread will be passed the message, and n arguments in its initialization. """ gevent.sleep(n) print(message)# Initialize a new Greenlet instance running the named function# foothread1 = Greenlet.spawn(foo, "Hello", 1)# Wrapper for creating and runing a new Greenlet from the named # function foo, with the passed argumentsthread2 = gevent.spawn(foo, "I live!", 2)# Lambda expressionsthread3 = gevent.spawn(lambda x: (x+1), 2)threads = [thread1, thread2, thread3]# Block until all threads complete.gevent.joinall(threads) |
在上面的程序里使用 spawn 方法来产生greenlet。还有一种初始化greenlet的方法,就是创建Greenlet的子类,并且重写 _run 方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import geventfrom gevent import Greenletclass MyGreenlet(Greenlet): def __init__(self, message, n): Greenlet.__init__(self) self.message = message self.n = n def _run(self): print(self.message) gevent.sleep(self.n)g = MyGreenlet("Hi there!", 3)g.start()g.join() |
Greenlet 的状态
就像其他的代码一样,greenlet在执行的时候也会出错。Greenlet有可能会无法抛出异常,停止失败,或者消耗了太多的系统资源。
greenlet的内部状态通常是一个依赖时间的参数。greenlet有一些标记来让你能够监控greenlet的状态。
- started -- 标志greenlet是否已经启动
- ready -- 标志greenlet是否已经被终止
- successful() -- 标志greenlet是否已经被终止,并且没有抛出异常
- value -- 由greenlet返回的值
- exception -- 在greenlet里面没有被捕获的异常
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
import geventdef win(): return 'You win!'def fail(): raise Exception('You fail at failing.')winner = gevent.spawn(win)loser = gevent.spawn(fail)print(winner.started) # Trueprint(loser.started) # True# Exceptions raised in the Greenlet, stay inside the Greenlet.try: gevent.joinall([winner, loser])except Exception as e: print('This will never be reached')print(winner.value) # 'You win!'print(loser.value) # Noneprint(winner.ready()) # Trueprint(loser.ready()) # Trueprint(winner.successful()) # Trueprint(loser.successful()) # False# The exception raised in fail, will not propogate outside the# greenlet. A stack trace will be printed to stdout but it# will not unwind the stack of the parent.print(loser.exception)# It is possible though to raise the exception again outside# raise loser.exception# or with# loser.get() |
这段代码的执行结果如下:
|
1
2
3
4
5
6
7
8
9
|
TrueTrueYou win!NoneTrueTrueTrueFalseYou fail at failing. |
终止程序
在主程序收到一个SIGQUIT 之后会阻塞程序的执行让Greenlet无法继续执行。这会导致僵尸进程的产生,需要在操作系统中将这些僵尸进程清除掉。
|
1
2
3
4
5
6
7
8
9
10
|
import geventimport signaldef run_forever(): gevent.sleep(1000)if __name__ == '__main__': gevent.signal(signal.SIGQUIT, gevent.shutdown) thread = gevent.spawn(run_forever) thread.join() |
超时
gevent提供了对与代码运行时的时间限制功能,也就是超时功能。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import geventfrom gevent import Timeoutseconds = 10timeout = Timeout(seconds)timeout.start()def wait(): gevent.sleep(10)try: gevent.spawn(wait).join()except Timeout: print 'Could not complete' |
也可以通过用with 上下文的方法来实现超时的功能:
|
1
2
3
4
5
6
7
8
9
10
|
import geventfrom gevent import Timeouttime_to_wait = 5 # secondsclass TooLong(Exception): passwith Timeout(time_to_wait, TooLong): gevent.sleep(10) |
gevent还提供了一些超时的参数以应对不同的状况:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import geventfrom gevent import Timeoutdef wait(): gevent.sleep(2)timer = Timeout(1).start()thread1 = gevent.spawn(wait)try: thread1.join(timeout=timer)except Timeout: print('Thread 1 timed out')# --timer = Timeout.start_new(1)thread2 = gevent.spawn(wait)try: thread2.get(timeout=timer)except Timeout: print('Thread 2 timed out')# --try: gevent.with_timeout(1, wait)except Timeout: print('Thread 3 timed out') |
运行结果如下:
|
1
2
3
|
Thread 1 timed outThread 2 timed outThread 3 timed out |
Monkeypatching
现在这是gevent里面的一个难点。下面一个例子里可能看到 monkey.patch_socket() 能够在运行时里面修改基础库socket:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import socketprint( socket.socket )print "After monkey patch"from gevent import monkeymonkey.patch_socket()print( socket.socket )import selectprint select.selectmonkey.patch_select()print "After monkey patch"print( select.select ) |
运行结果如下:
|
1
2
3
4
5
6
7
|
class 'socket.socket'After monkey patchclass 'gevent.socket.socket'built-in function selectAfter monkey patchfunction select at 0x1924de8 |
Python的运行时里面允许能够大部分的对象都是可以修改的,包括模块,类和方法。这通常是一个坏主意,然而在极端的情况下,当有一个库需要加入一些Python基本的功能的时候,monkey patch就能派上用场了。在上面的例子里,gevent能够改变基础库里的一些使用IO阻塞模型的库比如socket,ssl,threading等等并且把它们改成协程的执行方式。
Python gevent学习笔记的更多相关文章
- Python gevent学习笔记-2
在上一篇里面介绍了gevent的最主要的功能,先来来了解一下gevent里面一些更加高级的功能. 事件 事件是一种可以让greenlet进行异步通信的手段. ? 1 2 3 4 5 6 7 8 9 1 ...
- 【原】Learning Spark (Python版) 学习笔记(三)----工作原理、调优与Spark SQL
周末的任务是更新Learning Spark系列第三篇,以为自己写不完了,但为了改正拖延症,还是得完成给自己定的任务啊 = =.这三章主要讲Spark的运行过程(本地+集群),性能调优以及Spark ...
- Python Click 学习笔记(转)
原文链接:Python Click 学习笔记 Click 是 Flask 的团队 pallets 开发的优秀开源项目,它为命令行工具的开发封装了大量方法,使开发者只需要专注于功能实现.恰好我最近在开发 ...
- 0003.5-20180422-自动化第四章-python基础学习笔记--脚本
0003.5-20180422-自动化第四章-python基础学习笔记--脚本 1-shopping """ v = [ {"name": " ...
- Python Flask学习笔记之模板
Python Flask学习笔记之模板 Jinja2模板引擎 默认情况下,Flask在程序文件夹中的templates子文件夹中寻找模板.Flask提供的render_template函数把Jinja ...
- Python Flask学习笔记之Hello World
Python Flask学习笔记之Hello World 安装virtualenv,配置Flask开发环境 virtualenv 虚拟环境是Python解释器的一个私有副本,在这个环境中可以安装私有包 ...
- 获取字段唯一值工具- -ArcPy和Python案例学习笔记
获取字段唯一值工具- -ArcPy和Python案例学习笔记 目的:获取某一字段的唯一值,可以作为工具使用,也可以作为函数调用 联系方式:谢老师,135-4855-4328,xiexiaokui# ...
- Python高级学习笔记
Python高级学习笔记,此笔记中包含Linux操作系统.Html+CSS+JS.网络协议等. 所有思维导图为本人亲手所画,请勿用于商用. 大哥们,求点赞哦. 第一天笔记:链接 第二天笔记:链接 第三 ...
- Python入门学习笔记4:他人的博客及他人的学习思路
看其他人的学习笔记,可以保证自己不走弯路.并且一举两得,即学知识又学方法! 廖雪峰:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958 ...
随机推荐
- struts2之ModelDriven的用法
在Struts 2中,提供了另外一种直接使用领域对象的方式,就是让action实现com.opensymphony. xwork2.ModelDriven接口.ModelDriven让你可以直接操作应 ...
- 怎样在caffe中添加layer以及caffe中triplet loss layer的实现
关于triplet loss的原理.目标函数和梯度推导在上一篇博客中已经讲过了.详细见:triplet loss原理以及梯度推导.这篇博文主要是讲caffe下实现triplet loss.编程菜鸟.假 ...
- linux 输出重定向 何时会写文件
linux 输出重定向 何时会写文件 测试到了8K才会进行flush:
- JavaScript 数字与字符串 比较大小
总结一下JS中经常遇到纯数字和各种各样的字符串进行比较: 纯数字之间的比较 alert(1<3);//true 数字字符串比较,会将其先转成数字 alert("1"<& ...
- 深入解析淘宝Diamond之客户端架构
转载:http://blog.csdn.net/u013970991/article/details/52088350 一.什么是Diamond diamond是淘宝内部使用的一个管理持久配置的系统, ...
- C# 0-1背包问题
0-1背包问题 0-1背包问题基本思想: p[i,j]表示在前面i个物品总价值为j时的价值最大值.str[i, j]表示在前面i个物品总价值为j时的价值最大值时的物品重量串. i=0 或者j=0时: ...
- Python——实现代理服务功能
代理服务原理很简单,就拿浏览器与web服务器来说.无非是A浏览器发request给B代理,B代理再把request把送给C web服务,然后C的reponse->B->A.要写web代理服 ...
- Python——在Unicode和普通字符串之间转换
1.1. 问题 Problem You need to deal with data that doesn't fit in the ASCII character set. 你需要处理不适合用ASC ...
- cocos2d-x 重力感应 加速器的使用
CSDN开通已有两三年,今天作为一名刚入行的菜鸟写下自己的第一篇Blog. 刚好项目中须要用到重力感应,google下发现重力感应的使用很easy. 例如以下: 第一步: 在当前层开启重力感应.函数: ...
- MySQL 5.6数据导入报 GTID 相关错误
从阿里云备份数据后还原到本地,用命令行 mysql -uroot -p --default-character-set=<character> -f <dbname> < ...