1.基本介绍
一个基于greenlet的并发网络库。有了gevent后,不必像greenlet那样手动切换,而是当一个协程阻塞时,将自动切换到其他协程。
import gevent
def test1():
print('test1.start')
gevent.sleep(0)
print('test1.end')
def test2():
print('test2.start')
gevent.sleep(0)
print('test2.end')
gevent.joinall([
gevent.spawn(test1),
gevent.spawn(test2)
])
gevent.spawn()
会创建一个协程并且运行
gevent.sleep()
会造成阻塞,切换到其他协程继续执行
gevent.joinall([])
会等待所有传入的协程运行结束后再退出
(joinall()还可以接受一个timeout参数,设置超时时间)
与greenlet不同的是,当一个协程运行完成后,会自动切换到其他未完成的协程中运行,所以上段程序打印为
打印结果:
test1.start
test2.start
test1.end
test2.end
import gevent
import socket
urls = ['www.baidu.com','www.gevent.org','www.python.org','www.google.com','www.123.com']
jobs = [gevent.spawn(socket.gethostbyname,url) for url in urls]
gevent.joinall(jobs,timeout=5)
for job in jobs:
print(job.value)
通过协程来分别打开三个网址的IP地址,由于打开远程地址时造成IO阻塞,所以gevent会自动调度不同的协程
2.猴子补丁
python的socket库是阻塞式的,DNS无法并发解析,所以上一个例子中使用协程是起不到节省时间的目的的。
可以使用猴子补丁(Monkey Patching)来解决这个问题。
只需要在上一个例子最上面加入两行代码
from gevent import monkey
monkey.patch_all()
对socket标准库打上猴子补丁,此后socket标准库中的类和方法都变成了非阻塞式,其他的所有代码都不需要更改
这样协程的效率就能体现出来了
monkey.patch_all()将把python中所有的标准库都替换成非阻塞式
3.获取协程状态
started属性判断协程是否已启动
ready()函数判断是否已停止
successful()函数判断是否成功运行并且没有抛出异常
value属性,如果运行成功并且有返回值,那么可以通过value获取到
exception,在greenlet协程运行过程中抛出异常是不会抛出到协程外的,因此需要主动捕获异常
import gevent
def win():
return 'You Win'
def fail():
raise Exception('You Fail')
winner = gevent.spawn(win)
loser = gevent.spawn(fail)
print('winner.started =',winner.started)
print('loser.started =',loser.started)
# 在Greenlet中发生的异常,不会被抛到Greenlet的外面
# 控制台会打印错误信息,但程序不会中断
try:
gevent.joinall([
winner,
loser
])
except Exception as e:
# 这段不会执行
print(e.args)
print('winner.ready() =',winner.ready())
print('loser.ready() =',loser.ready())
print('winner.value =',winner.value)
print('loser.value =',loser.value)
print('winner.successful() =',winner.successful())
print('loser.successful() =',loser.successful())
# 获取异常信息
print('loser.exception =',loser.exception)
# get函数会将loser的异常抛出,如果不捕获,程序会中断
try:
print(loser.get())
except:
print('exception from loser')
打印LOG:
winner.started = True
loser.started = True
Traceback (most recent call last):
File "E:\Documents\Anaconda\lib\site-packages\gevent\greenlet.py", line 536, in run
result = self._run(*self.args, **self.kwargs)
File "E:\Code\Python\Test\test6.py", line 7, in fail
raise Exception('You Fail')
Exception: You Fail
Sun Jul 8 17:31:11 2018 <Greenlet at 0x142a32a79c8: fail> failed with Exception
winner.ready() = True
loser.ready() = True
winner.value = You Win
loser.value = None
winner.successful() = True
loser.successful() = False
loser.exception = You Fail
exception from loser
4.协程运行超时
前面将可以在gevent.joinall()函数传入timeout参数来设置超时时间,也可以设置全局超时时间
import gevent
from gevent import Timeout
timeout = Timeout(2)
timeout.start()
def wait():
gevent.sleep(10)
try:
gevent.spawn(wait).join()
except Timeout:
print('Could not complete')
在这个例子中,所有的协程的超时时间都设置成2秒,如果2秒内协程没有结束,都将抛出Timeout异常
也可以将超时设置在with语句内,那么设置就只在with内部生效了
with Timeout(2):
gevent.sleep(10)
也可以设置当超时时抛出指定的异常
with Timeout(2,Exception):
gevent.sleep(10)
5.协程间通讯
wait()可以阻塞协程
set()可以唤醒所有阻塞的异常
import gevent
from gevent.event import Event
evt = Event()
def setter():
print('Wait for me')
gevent.sleep(3)
print('After 3 sec, I am done')
evt.set()
def waiter():
print('I am waiting')
evt.wait()
print('Finish waiting')
gevent.joinall([
gevent.spawn(setter),
gevent.spawn(waiter).spawn(waiter).spawn(waiter).spawn(waiter).spawn(waiter)
])
当调用Event.wait()函数时,协程会进入阻塞状态,直到调用set()函数后被唤醒
除了Event时间外,AsyncResult也有该功能,并且能在唤醒时传递数据
import gevent
from gevent.event import AsyncResult
aevt = AsyncResult()
def setter():
print('Waitint for me')
gevent.sleep(3)
print('After 3 sec, I am done')
aevt.set(value='Get Up')
def waiter():
print('I am waiting')
# 进入等待状态,同时等待接收数据
# 如果没有传入数据,那么value值为None
value = aevt.get()
print('I get a value:',value)
gevent.joinall([
gevent.spawn(setter),
gevent.spawn(waiter).spawn(waiter)
])
6.信号量
信号量可以限制协程的数量
acquire()获取信号量
release()释放信号量
当所有的信号量都已经被获取后,剩下的协程只能等待某个释放才能继续执行
如果信号量的数量为1,那么久相当于同步锁
import gevent
from gevent.lock import BoundedSemaphore
sem = BoundedSemaphore(2)
def worker(r):
sem.acquire()
print(f'Worker {r} acquire a sem')
gevent.sleep(2)
sem.release()
print(f'Worker {r} release a sem')
gevent.joinall([
gevent.spawn(worker,i) for i in range(6)
])
7.协程本地变量
将变量放在local()中,即可将其作用域限制在该协程内,当其他协程想要访问另一协程的变量时就会报错
不同协程间可以有相同名称的变量,互不影响
import gevent
from gevent.local import local
data = local()
def f1():
data.x = 'f1'
print(data.x)
def f2():
try:
print(data.x)
except:
print('f2 has not data.x')
gevent.joinall([
gevent.spawn(f1).spawn(f2)
])