目录
- 十二、Python中协程
- 12.1 协程的定义
- 12.2 协程
- 12.3 协程池
- 12.4 总结
十二、Python中协程
12.1 协程的定义
协程(Coroutine):是一种比线程更加轻量级的存在,也称微线程,协程可以理解为一个特殊的函数,这个函数可以在某个地方挂起去执行别的,并且可以返回挂起处继续执行,线程数量越多协程的性能优势越明显,多进程和协程的组合能充分利用计算机的多核处理。
12.2 协程
Python对协程的支持常用的有yield
,greenlet
gevent
三种方式,进行协程的调用。
yeild 演示如下 -
from time import sleep
def study_math():
for _ in range(5):
print("study_math")
yield
sleep(0.5)
def study_chemisty():
for _ in range(5):
print("study_chemisty")
yield
sleep(0.5)
def main():
study1 = study_math()
study2 = study_chemisty()
for _ in range(3):
study1.__next__()
study2.__next__()
if __name__ == '__main__':
main()
上面脚本的执行结果 -
study_math
study_chemisty
study_math
study_chemisty
study_math
study_chemisty
greenlet 演示如下 -
对协程进行简单封装,该模块需要重新安装,在cmd命令行长输入“pin install greenlet ”即可
from time import sleep
from greenlet import greenlet
def study_math():
for _ in range(3):
print("study_math")
glt2.switch()
sleep(0.5)
def study_chemisty():
for _ in range(3):
print("study_chemisty")
glt1.switch()
sleep(0.5)
glt1 = greenlet(study_math)
glt2 = greenlet(study_chemisty)
glt1.switch()
glt2.switch()
上面脚本的执行结果 -
study_math
study_chemisty
study_math
study_chemisty
study_math
study_chemisty
gevent 演示如下 -
greenlet
虽然实现协程,但需要手动切换,相当麻烦;这里提供gevent
自动切换,在cmd命令行长输入“pin install gevent”即可。
在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程,因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。
这里重点介绍 monkey
函数
from gevent import monkey
import gevent
from urllib.request import urlopen
monkey.patch_all()
def f(url):
print('GET: %s' % url)
resp = urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(f, 'https://www.tianya.cn'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://www.baidu.com/'),
])
上面脚本的执行结果 -
GET: https://www.tianya.cn
GET: https://www.yahoo.com/
GET: https://www.baidu.com/
227 bytes received from https://www.baidu.com/.
7646 bytes received from https://www.tianya.cn.
3369 bytes received from https://www.yahoo.com/.
12.3 协程池
gevent 常用方法如下 -
名称 | 说明 |
gevent.spawn() | 创建一个普通的Greenlet对象并切换 |
gevent.spawn_later(seconds=3) | 延时创建一个普通的Greenlet对象并切换 |
gevent.spawn_raw() | 创建的协程对象属于一个组 |
gevent.getcurrent() | 返回当前正在执行的greenlet |
gevent.joinall(jobs) | 将协程任务添加到事件循环,接收一个任务列表 |
gevent.wait() | 可以替代join函数等待循环结束,也可以传入协程对象列表 |
gevent.kill() | 杀死一个协程 |
gevent.killall() | 杀死一个协程列表里的所有协程 |
monkey.patch_all() | 非常重要,自动将 python 的一些标准模块替换成 gevent 框架设置强制切换的时间 |
sys.setcheckinterval(n) | 每n条执行尝试进行线程切换,n必须是int |
sys.getswitchinterval() | 默认5ms切换 |
名称 | 说明 |
job | Greenlet(target0, 3) Greenlet对象创建 |
job.start() | 将协程加入循环并启动协程 |
job.start_later(3) | 延时启动 |
job.join() | 等待任务完成 |
job.get() | 获取协程返回的值 |
job.dead() | 判断协程是否死亡 |
job.kill() | 杀死正在运行的协程并唤醒其他的协程,这个协程将不会再执行 |
job.ready() | 任务完成返回一个真值 |
job.successful() | 任务成功完成返回真值,否则抛出错误 |
job.loop | 时间循环对象 |
job.value | 获取返回的值 |
job.exception | 如果运行有错误,获取它异常信息 |
import gevent
from gevent import socket
from gevent.pool import Pool
N = 1000
# limit ourselves to max 10 simultaneous outstanding requests
pool = Pool(2)
finished = 0
def job(url):
global finished
try:
try:
ip = socket.gethostbyname(url)
print("{url} = {ip}")
except socket.gaierror as e:
print(f"{url} failed with {e}")
finally:
finished += 1
with open("urls.txt", 'r') as f:
with gevent.Timeout(10, False):
for x in f:
line = x.rstrip('\r\n')
pool.spawn(job, f"{line}")
pool.join()
print(f"finished within 10 seconds: {finished}/{N}")
12.4 总结
协程常用于IO密集型工作,例如网络资源请求等;而进程、线程常用于计算密集型工作,例如科学计算、人工神经网络等。