协程框架---gevent的基本介绍

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 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)
    ])

你可能感兴趣的:(协程框架---gevent的基本介绍)