Gevent框架性能很高,但一直以来我都纠结在python的GIL模型导致的线程不能抢占多核资源上面。
而启动多个python进程的这种利用多核的模式又需要增加前端负载均衡,比如lvs那些,有些麻烦。
multiprocessing模块和os.fork又会使得两个进程重复在事件核心注册accept事件,导致文件句柄重复的异常。
至于一个进程监听,多个进程处理的模式,监听的那个进程资源又不好分配——是独立分配一个核心还是不单独分配呢?如果单独分配,连接量小的时候就浪费了一个核心,如果不分配,连接量大的时候cpu又会频繁切换进程。
昨日才发现原来gevent是可以很轻松地将它的网络模型分布到多个进程并行处理的。
秘诀就在gevent.fork()。
以前想当然地认为gevent.fork只是greenlet.spawn的一个包装,原来不是这样。gevent.fork能替代os.fork,不仅会启动一个新的进程,而且能将它们底层的事件处理沟通起来,进行并行处理。
01 | import gevent |
02 | from gevent.server import StreamServer |
03 |
04 | def eat_cpu(): |
05 | for i in xrange ( 10000 ): pass |
06 |
07 | def cb(socket, address): |
08 | eat_cpu() |
09 | socket.recv( 1024 ) |
10 | socket.sendall( 'HTTP/1.1 200 OK\n\nHello World!!' ) |
11 | socket.close() |
12 |
13 | server = StreamServer(('', 80 ), cb, backlog = 100000 ) |
14 | server.pre_start() |
15 |
16 | gevent.fork() |
17 |
18 | server.start_accepting() |
19 | server._stopped_event.wait() |
打上monkey.patch_os后,os.fork就可以被gevent.fork替代了,这样同时multiprocessing模块也可以像往常一样使用,并达到并行处理的效果了。
01 | from gevent import monkey; monkey.patch_os() |
02 | from gevent.server import StreamServer |
03 | from multiprocessing import Process |
04 |
05 | def eat_cpu(): |
06 | for i in xrange ( 10000 ): pass |
07 |
08 | def cb(socket, address): |
09 | eat_cpu() |
10 | socket.recv( 1024 ) |
11 | socket.sendall( 'HTTP/1.1 200 OK\n\nHello World!!' ) |
12 | socket.close() |
13 |
14 | server = StreamServer(('', 80 ), cb, backlog = 100000 ) |
15 | server.pre_start() |
16 |
17 | def serve_forever(): |
18 | server.start_accepting() |
19 | server._stopped_event.wait() |
20 |
21 | process_count = 4 |
22 |
23 | for i in range (process_count - 1 ): |
24 | Process(target = serve_forever, args = tuple ()).start() |
25 |
26 | serve_forever() |
简单地作了一下测试
测试环境:
CPU AMD Athlon(tm) II X4 640 3.01GHz
内存 2G DDR2
操作系统 CentOS 6.0 (虚拟机内)
测试命令 ab -n 10000 -c 100 http://127.0.0.1/
测试结果:
1个进程 1534.13 个请求每秒 是一个进程的1倍
2个进程 3056.48 个请求每秒 是一个进程的1.99倍
3个进程 4460.04 个请求每秒 是一个进程的2.90倍
由于ab需要单独用一个核心,所以就只测试3个进程的结果。
可以看到每秒处理的请求数随着进程数的提升也会有几乎成倍的提升。