3.4.2 Gevent
Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
示例
import gevent
#gevent,自动挡切换
def func1():
print('Ashley starts running.') #1
gevent.sleep(2) #切换到2
print('Ashley tumbled over Sarah.') #6
def func2():
print('Sarah starts running.') #2
gevent.sleep(1) #切换到3
print('Sarah tumbled over Nancy.') #5
def func3():
print('Nancy starts running.') #3
gevent.sleep(0) #切换,但无需等待
print('Nancy tumbled on a hurdle.') #4
gevent.joinall([
gevent.spawn(func1), #spawn引发引起导致造成
gevent.spawn(func2),
gevent.spawn(func3)
])结果
Ashley starts running. Sarah starts running. Nancy starts running. Nancy tumbled on a hurdle. Sarah tumbled over Nancy. Ashley tumbled over Sarah.
同步与异步的性能区别
看下面示例
import gevent
def task(pid):
gevent.sleep(0.5)
print('Task %s done' % pid)
def synchronous():
for i in range(1, 10):
task(i) #串行,循环10次task函数
def asynchronous():
threads = [gevent.spawn(task, i) for i in range(10)]
#发起协程,协程执行task函数,循环10次,总计10条线程,存储在threads里
gevent.joinall(threads)
print('Synchronous:')
synchronous() #同步串行
print('Asynchronous:')
asynchronous() #异步并行结果
Synchronous: #这里每条睡0.5秒 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 1 done Task 2 done Task 3 done Task 4 done Task 5 done Task 6 done Task 7 done Task 8 done
上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。
遇到IO阻塞时会自动切换任务,下面是一个简单的爬取网页的示例,我们看一下同步与异步的区别:
import gevent, time
from gevent import monkey #gevent无法识别socket中的I/O操作
from urllib import request
monkey.patch_all() #有了这个方法,相当于给socket的I/O操作加上了标识
def f(url):
print('GET: %s' % url)
resp = request.urlopen(url)
data = resp.read() #I/O任务,每当遇到I/O任务会自动切换
print('%d bytes received from %s.' % (len(data), url))
# print('Data from %s is: %s.' % (url, data))
urls = ['https://www.python.org/',
'https://www.yahoo.com/',
'https://github.com/']
sync_start_time = time.time()
for url in urls:
f(url)
print('同步Cost: ', time.time() - sync_start_time)
async_start_time = time.time()
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'), #启动协程,执行f函数,并将url传递给f
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
print('异步Cost: ', time.time() - async_start_time)结果
GET: https://www.python.org/ 48981 bytes received from https://www.python.org/. GET: https://www.yahoo.com/ 334724 bytes received from https://www.yahoo.com/. GET: https://github.com/ 132874 bytes received from https://github.com/. 同步Cost: 25.191901445388794 GET: https://www.python.org/ GET: https://www.yahoo.com/ GET: https://github.com/ 48981 bytes received from https://www.python.org/. 132874 bytes received from https://github.com/. 334179 bytes received from https://www.yahoo.com/. 异步Cost: 5.816762208938599
由于每次遇到I/O操作都会自动切换,所以,先输出三条print(‘GET: %s’ % url),当遇到data = resp.read()这步时会自动切换。read()读取属于I/O操作。
通过gevent实现单线程下的多socket并发
服务器端
import gevent
from gevent import socket, monkey
monkey.patch_all()
def server(port): #port=8001
s = socket.socket() #实例化一个socket
host = socket.gethostname()
s.bind((host, port))
while True:
client, addr = s.accept()
gevent.spawn(handle_request, client) #客户端每来一条链接启动一个协程
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print('Received data: ', data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as e:
print(e)
finally:
conn.close()
if __name__ == '__main__':
server(8001) #调用server并传参8001客户端
import socket
host = socket.gethostname()
port = 8001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
while True:
msg = bytes(input('\>: '), encoding='UTF-8')
s.sendall(msg)
data = s.recv(1024)
print('Received: ', repr(data))为了显示多并发结果,这里我们同时并行三个客户端
服务器端
Received data: b'\xe8\x80\x8c\xe6\xaf\x8f\xe8\xbf\x87\xe4\xb8\x80\xe5\xa4\xa9\xe6\xaf\x8f\xe4\xb8\x80\xe5\xa4\xa9\xe8\xbf\x99\xe9\x86\x89\xe8\x80\x85' Received data: b'\xe4\xbe\xbf\xe7\x88\xb1\xe4\xbd\xa0\xe5\xa4\x9a\xe4\xba\x9b\xe5\x86\x8d\xe5\xa4\x9a\xe4\xba\x9b\xe8\x87\xb3\xe6\xbb\xa1\xe6\xb3\xbb' Received data: b'\xe6\x88\x91\xe5\x8f\x91\xe8\xa7\x89\xe6\x88\x91\xe6\x9c\x80\xe7\x88\xb1\xe4\xb8\x8e\xe4\xbd\xa0\xe7\xbc\x96\xe5\x86\x99\xe4\xbb\xa5\xe5\x90\x8e\xe6\x98\x8e\xe5\xa4\xa9\xe7\x9a\x84\xe6\xb7\xb1\xe5\xa4\x9c'
客户端1
\>: 而每过一天每一天这醉者 Received: b'\xe8\x80\x8c\xe6\xaf\x8f\xe8\xbf\x87\xe4\xb8\x80\xe5\xa4\xa9\xe6\xaf\x8f\xe4\xb8\x80\xe5\xa4\xa9\xe8\xbf\x99\xe9\x86\x89\xe8\x80\x85' \>:
客户端2
\>: 便爱你多些再多些至满泻 Received: b'\xe4\xbe\xbf\xe7\x88\xb1\xe4\xbd\xa0\xe5\xa4\x9a\xe4\xba\x9b\xe5\x86\x8d\xe5\xa4\x9a\xe4\xba\x9b\xe8\x87\xb3\xe6\xbb\xa1\xe6\xb3\xbb' \>:
客户端3
\>: 我发觉我最爱与你编写以后明天的深夜 Received: b'\xe6\x88\x91\xe5\x8f\x91\xe8\xa7\x89\xe6\x88\x91\xe6\x9c\x80\xe7\x88\xb1\xe4\xb8\x8e\xe4\xbd\xa0\xe7\xbc\x96\xe5\x86\x99\xe4\xbb\xa5\xe5\x90\x8e\xe6\x98\x8e\xe5\xa4\xa9\xe7\x9a\x84\xe6\xb7\xb1\xe5\xa4\x9c' \>:
以上就实现了一个多并发服务器。
浙公网安备 33010602011771号