最近开始研究Python的并行开发技术,包括多线程,多进程,协程等。逐步整理了网上的一些资料,今天整理一下greenlet相关的资料。python
并行化处理目前很受重视, 由于在不少时候,并行计算能大大的提升系统吞吐量,尤为在如今多核多处理器的时代, 因此像lisp这种古老的语言又被人们从新拿了起来, 函数式编程也愈来愈流行。 介绍一个python的并行处理的一个库: greenlet。 python 有一个很是有名的库叫作 stackless ,用来作并发处理, 主要是弄了个叫作tasklet的微线程的东西, 而greenlet 跟stackless的最大区别是, 他很轻量级?不够, 最大的区别是greenlet须要你本身来处理线程切换, 就是说,你须要本身指定如今执行哪一个greenlet再执行哪一个greenlet。程序员
之前使用python开发web程序,一直使用的是fastcgi模式.而后每一个进程中启动多个线程来进行请求处理.这里有一个问题就是须要保证每一个请求响应时间都要特别短,否则只要多请求几回慢的就会让服务器拒绝服务,由于没有线程可以响应请求了.平时咱们的服务上线都会进行性能测试的,因此正常状况没有太大问题.可是不可能全部场景都测试到.一旦出现就会让用户等很久没有响应.部分不可用致使所有不可用.后来转换到了coroutine,python 下的greenlet.因此对它的实现机制作了一个简单的了解.
每一个greenlet都只是heap中的一个python object(PyGreenlet).因此对于一个进程你建立百万甚至千万个greenlet都没有问题.web
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef struct _greenlet {
PyObject_HEAD
char
*
stack_start;
char
*
stack_stop;
char
*
stack_copy;
intptr_t stack_saved;
struct _greenlet
*
stack_prev;
struct _greenlet
*
parent;
PyObject
*
run_info;
struct _frame
*
top_frame;
int
recursion_depth;
PyObject
*
weakreflist;
PyObject
*
exc_type;
PyObject
*
exc_value;
PyObject
*
exc_traceback;
PyObject
*
dict
;
} PyGreenlet;
|
每个greenlet其实就是一个函数,以及保存这个函数执行时的上下文.对于函数来讲上下文也就是其stack..同一个进程的全部的greenlets共用一个共同的操做系统分配的用户栈.因此同一时刻只能有栈数据不冲突的greenlet使用这个全局的栈.greenlet是经过stack_stop,stack_start来保存其stack的栈底和栈顶的,若是出现将要执行的greenlet的stack_stop和目前栈中的greenlet重叠的状况,就要把这些重叠的greenlet的栈中数据临时保存到heap中.保存的位置经过stack_copy和stack_saved来记录,以便恢复的时候从heap中拷贝回栈中stack_stop和stack_start的位置.否则就会出现其栈数据会被破坏的状况.因此应用程序建立的这些greenlet就是经过不断的拷贝数据到heap中或者从heap中拷贝到栈中来实现并发的.对于io型的应用程序使用coroutine真的很是舒服.编程
下面是greenlet的一个简单的栈空间模型(from greenlet.c)安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
A PyGreenlet
is
a
range
of C stack addresses that must be
saved
and
restored
in
such a way that the full
range
of the
stack contains valid data when we switch to it.
Stack layout
for
a greenlet:
| ^^^ |
| older data |
| |
stack_stop . |_______________|
. | |
. | greenlet data |
. |
in
stack |
.
*
|_______________| . . _____________ stack_copy
+
stack_saved
. | | | |
. | data | |greenlet data|
. | unrelated | | saved |
. | to | |
in
heap |
stack_start . | this | . . |_____________| stack_copy
| greenlet |
| |
| newer data |
| vvv |
|
下面是一段简单的greenlet代码.服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from
greenlet
import
greenlet
def
test1():
print
12
gr2.switch()
print
34
def
test2():
print
56
gr1.switch()
print
78
gr1
=
greenlet(test1)
gr2
=
greenlet(test2)
gr1.switch()
|
目前所讨论的协程,通常是编程语言提供支持的。目前我所知提供协程支持的语言包括python,lua,go,erlang, scala和rust。协程不一样于线程的地方在于协程不是操做系统进行切换,而是由程序员编码进行切换的,也就是说切换是由程序员控制的,这样就没有了线程所谓的安全问题。
全部的协程都共享整个进程的上下文,这样协程间的交换也很是方便。
相对于第二种方案(I/O多路复用),使得使用协程写的程序将更加的直观,而不是将一个完整的流程拆分红多个管理的事件处理。
协程的缺点多是没法利用多核优点,不过,这个能够经过协程+进程的方式来解决。
协程能够用来处理并发来提升性能,也能够用来实现状态机来简化编程。我用的更多的是第二个。去年年末接触python,了解到了python的协程概念,后来经过pycon china2011接触处处理yield,greenlet也是一个协程方案,并且在我看来是更可用的一个方案,特别是用来处理状态机。
目前这一块已经基本完成,后面抽时间总结一下。多线程
总结一下:
1)多进程可以利用多核优点,可是进程间通讯比较麻烦,另外,进程数目的增长会使性能降低,进程切换的成本较高。程序流程复杂度相对I/O多路复用要低。
2)I/O多路复用是在一个进程内部处理多个逻辑流程,不用进行进程切换,性能较高,另外流程间共享信息简单。可是没法利用多核优点,另外,程序流程被事件处理切割成一个个小块,程序比较复杂,难于理解。
3)线程运行在一个进程内部,由操做系统调度,切换成本较低,另外,他们共享进程的虚拟地址空间,线程间共享信息简单。可是线程安全问题致使线程学习曲线陡峭,并且易出错。
4)协程有编程语言提供,由程序员控制进行切换,因此没有线程安全问题,能够用来处理状态机,并发请求等。可是没法利用多核优点。
上面的四种方案能够配合使用,我比较看好的是进程+协程的模式。并发