协程
首先要了解两个概念
-
上下文
在每个任务运行前,CPU都需要知道任务从哪里加载,又是从哪里开始运 行,也就是说,需要系统事先帮他设置好CPU寄存器和程序计数器(Program Counter,PC)
CPU寄存器,是CPU内置的容量小、但速度极快的内存。程序计数器,则是用来存储CPU正在执行的指令的位置,或者即将执行的下一条指令的位置。他们都是CPU在运行任何任务前,必须依赖的环境,因此也被叫做CPU上下文。
知道了什么是CPU上下文,也就很容易理解CPU上下文切换。CPU上下文切换,就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文,到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务
-
IO操作
网络io: 获取或者上传网络数据(下载或者上传)
磁盘io : 读取写入文件
io操作比较耗时,如果程序io设计不好,非常影响性能
了解了上面两个概念 然后引入协程
-
协程 又称 微线程,纤程
-
协程的实际概念
协程是python中实现多任务的一种方式,是比线程跟小的执行单元,
因为它自带CPU上下文,会在合适的时机,把一个协程切换到另一个协程,所以它是有一个执行单元,并且这个过程中保存或恢复CPU上下文的程序还是可以运行。这个合适的时机很重要由开发者自己确定
-
下面我们使用简单的方式实现协程
import time def test1(): while True: print("text1") yield # 一个耗时操作 合适的时机 time.sleep(0.5) def test2(): while True: print("text2") yield # 一个耗时操作 合适的时机 time.sleep(0.5) t1 = test1() t2 = test2() while True: next(t1) next(t2)
应该很容易看出这个运行结果, 如果不知道yield,可以先去去了解一下生成器这个概念。
这个就是协程的简单实现,然而我们日常用的一般是已经封装好的
-
gevent
除了gevent还有一个greenlet也实现了协程,但是他还是需要人工切换。
gevent是一个可以自行切换任务的模块,它的原理是当一个程序遇到IO操作时,会切换协程,保证别的程序还能运行,而不是等待IO,从而浪费了等待的时间。简单实现
import gevent def test1(n): for i in range(n): print('test1----', i) # 模拟一个耗时操作 必须用gevent的sleep(1) gevent.sleep(1) t1 = gevent.spawn(test1, 5) t2 = gevent.spawn(test1, 5) t3 = gevent.spawn(test1, 5) t1.join() t2.join() t3.join() # 下面方式与上面相同 #gevent.joinall([ # gevent.spawn(test1, 5), # gevent.spawn(test1, 5), # gevent.spawn(test1, 5) #])
运行结果
具体运行效果建应自行尝试
一般情况下,为了避免阻塞自动切换协程,程序启动时要执行 monkey.patch_all()解决
import time
import gevent
from gevent import monkey
monkey.patch_all()
def test1(n):
for i in range(n):
print('test1----', i)
# 因为 执行了 monkey.path_all() 所以当使用time来模拟耗时时也开始实现
time.sleep(1)
gevent.joinall([
gevent.spawn(test1, 5),
gevent.spawn(test1, 5),
gevent.spawn(test1, 5)
]
)
协程的大致情况就是如此,接下来我会在写一个用协程实现的并发下载器
🐖注:纸上得来终觉浅,绝知此事要躬行