协程,又称微线程,是一种用户态的轻量级线程
协程拥有自己的寄存器上下文和栈,在上下文切换的时候不需要占用CPU(线程上下文切换占用CPU)
协程的好处:
- 无需线程上下文切换时的开销,
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高拓展性+低成本
缺点:
- 无法利用多核资源
- 进行阻塞操作时会阻塞整个程序
明白了它的原理,就能明白它的这些优缺点:
协程的本质还是一条线程,但是一条线程中可以开启上千上万个协程,协程上下文切换有自己的储存空间,所以不占用CPU,每次遇到IO操作,程序就会进行上下文的切换,当IO操作完成, 上下文又会切换回来,所以效率也很高,因为归根结底还是一个线程,所以无法利用多核资源,但是和进程配合就可以了,另外,因为是单线程,所以阻塞的时候程序会停止运行
先看一个使用yield实现协程操作的例子
# 生成器
def consumer(name):
print('start eating baozi...')
while True:
new_baozi = yield
print('[%s] is eating baozi %s' % (name, new_baozi))
def producer():
# 执行生成器中的代码
r = con.__next__()
r = con2.__next__()
n = 0
while n < 5:
n += 1
# send的作用恢复生成器的同时传送一个值,这里指的就是new_baozi
con.send(n)
con2.send(n)
print('\033[32;1m[producer]\033[0m is making baozi %s' % n)
if __name__ == '__main__':
con = consumer('c1')
con2 = consumer('c2')
p = producer()
两个生成器,使用send方法执行生成器剩下的代码和恢复生成器,这里只是模拟一下协程的工作方式,并不是标准的协程, 下面用greenlet来手动模拟一下协程
from greenlet import greenlet
def test1():
print(1)
# 跳到test2打印2
gr2.switch()
print(3)
# 跳到test2打印4
gr2.switch()
def test2():
print(2)
# 跳到test1打印3
gr1.switch()
print(4)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 跳到test1打印1
gr1.switch()
这里的greenlet实现了一个线程中上下文的切换的功能,但是还是没有真正的实现协程,因为还没有实现遇到IO操作就切换,下面就用到了gevent,而gevent就封装了greenlet,所以说你安装了gevent就可以使用greenlet了
Gevent是一个第三方库,可以轻松通过gevent实现并发同步或者异步编程,在gevent中用到的主要模式是Greenlet,它是以C扩展模块形式嵌入Python的轻量级协议,Greenlet会全部运行在主程序操作系统进程的内部,协作式调度
先看看用法
import gevent
def func1():
print('\033[31;1m我是func1里的\033[0m')
# IO操作
gevent.sleep(2)
print('\033[31;1m睡完了\033[0m')
def func2():
print('\033[32;1m我是func2里的\033[0m')
# IO操作
gevent.sleep(1)
print('\033[32;1m睡完了\033[0m')
# 列表形式传入joinall方法
gevent.joinall([
# spawn 产卵 传入函数名
gevent.spawn(func1),
gevent.spawn(func2),
])
通过这段可以看出程序是并发执行的,从输出可以看出来,当然自己跑一下会更明显,func2里sleep时间短,所以会先‘睡完’,这样就实现了智能的遇到IO操作就切换
可以跑一下这段代码体会一下协程的高效
import gevent
def task(pid):
"""
Some task
"""
gevent.sleep(0.5)
print('Task %s done' % pid)
# 串行
def test1():
for i in range(1, 10):
task(i)
# 并发
def test2():
threads = [gevent.spawn(task, i) for i in range(10)]
gevent.joinall(threads)
print('test1:')
test1()
print('test2:')
test2()
但是啊,gevent有的时候跟一些库配合的时候会检测不到他们的IO操作,所以这个时候就要使用gevent库的一些额外功能了,例如:
from gevent import monkey
import gevent
from urllib.request import urlopen
# 检测一些原先检测不到的IO操作
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))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
使用了gevent自带的monkey.patch_all()就可以检测到这些库的IO操作了
转载请注明出处
python自学技术互助扣扣群:670402334