使用gevent库来将python中的同步函数转化成多线程函数
将requests库转换成异步
之前使用了多线程的方式将requests库转换成异步等待,但是由于线程开销较高,且本质上还是在空等,所以使用协程来实现异步等待会更好。
gevent库
gevent库是一个网络库,它比asyncio标准库还要快一点。而且可以将同步函数直接转换成协程函数(其内置了loop)。
猴子补丁
它主要使用的方式就是猴子补丁,原理大致如下:
1 2 3 4 5
| import liba
import libb as liba
|
这样liba就被替换成了libb了,只要libb在接口上保证和liba的接口一致,那么就可以直接替换。
猴子补丁就是实现上述过程的东西。下面是猴子补丁使用
1 2 3 4 5 6 7 8
| import socket print(socket.socket)
from gevent import monkey monkey.patch_socket() print(socket.socket)
|
就是gevent
底层是libev
,它从其之上再造了一个socket
模块,具有跟标准库socket
兼容的接口,而gevent
中的socket
是运行在event loop
中的(标准库的socket不带)。
这样打完补丁后,socket库就是协程实现的了。
而requests基于urllib3,
urllib3基于socket,补丁是对socket的封装,所以requests库此时也成为协程异步的了。
API
下面有一些gevent常用的API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
gevent.spawn()
gevent.spawn_later(seconds=3)
gevent.spawn_raw()
gevent.getcurrent()
gevent.joinall(jobs)
gevent.wait()
gevent.kill()
gevent.killall()
monkey.patch_all()
|
使用gevent.spawn()
创建了一个协程会自动执行,如果想要获取该协程的结果最好先gevent.join()
另外,gevent
本身也是基于greenlet
的,所以也可以使用greenlet
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
from gevent import Greenlet
job = Greenlet(target0, 3) Greenlet.spawn() Greenlet.spawn_later(seconds=3)
job.start() job.start_later(3)
job.join() job.get()
job.dead() job.kill() job.ready() job.successful()
job.loop job.value
job.exception job.exc_info
job.rawlink(back) job.unlink()
job.link_value(back)
job.link_exception(back)
|
当然,与线程类似,协程也存在协程池。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
pool.wait_available()
pool.dd(greenlet)
pool.discard(greenlet)
pool.start(greenlet)
pool.join()
pool.kill()
pool.killone(greenlet)
|
协程池启动一个协程时,也是非阻塞的。
异步requests
使用requests库时,先创建协程对象,然后使用gevent.joinall()
来等待所有协程对象完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from gevent import monkey monkey.patch_all() import requests import gevent import time
def f(url,i): data = requests.get(url) print(f"{i} done")
start = time.time() url_list = [] for i in range(5): url_list.append(gevent.spawn(f, 'https://www.baidu.com/',i))
gevent.joinall(url_list) print(time.time() - start)
|
使用装饰器来创建协程函数
当然使用一个装饰器来隐式的完成创建协程对象会更方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from gevent import monkey, pool, Greenlet monkey.patch_all() import time import requests
p = pool.Pool(5)
def async_d(func): def warp_(*args,**kargs): p.start(Greenlet(func, *args, **kargs)) return warp_
@async_d def f(url,i): data = requests.get(url) print(f"{i} done")
start = time.time() for i in range(5): f('https://www.baidu.com/',i) p.join() print(time.time() - start)
|