目录
一、协程
协程:又称微线程,纤程。是python中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。为啥说它是一个执行单元,因为它自带cpu上下文。这样只要在合适的时机,我们可以把一个协程切换到另外一个协程。只要这个保存或恢复cpu上下文那么程序还是可以运行的。通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方法做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
二、协程和线程、进程的差异
①在实现多任务时,线程切换从系统层面远不止保存和恢复cpu上下文这么简单,操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。但是协程的切换只是单纯的操作cpu的上下文,所以一秒钟切换个上百万次系统都扛得住。
②一个程序,为了完成多任务,可以由多进程来实现,假如说进程数是10,那么此时有10个任务一起运行,一个进程中可以开线程,假如说每个进程有10个线程,那么一共有10x10==》100个任务一起运行。一个线程中可以开协程,假如说每个线程有10个协程,那么一共有10x10x10==》1000个任务一起运行
③计算密集型任务:使用多进程
io密集型任务:使用多线程、多协程
④进程、线程创建完之后,到底是哪个进程、线程执行是不确定的,这要让操作系统来进行计算(调度算法,例如优先级调度)。协程是人为可以控制的
三、使用yield实现协程
import time
def work1():
while True:
print("-----work1-----")
yield
time.sleep(0.5)
def work2():
while True:
print("-----work2-----")
yield
time.sleep(0.5)
def main():
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
if __name__ == '__main__':
main()
import time
def work1():
while True:
print("-----work1-----")
time.sleep(0.5)
def work2():
while True:
print("-----work2-----")
time.sleep(0.5)
def main():
while True:
work1()
work2()
if __name__ == '__main__':
main()
对比可以发现,用yield实现多协程可以使work1和work2交替执行。
四、使用greenlet来实现协程
from greenlet import greenlet
import time
def test1():
while True:
print("-----A-----")
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print("-----B-----")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 切换到gr1中运行。如果这里是gr2.switch()的话,那么test2()先执行。就会先打印-----B-----
gr1.switch()
协程是可以人为控制哪个协程执行
五、gevent来实现协程
greenlet已经实现了协程,但是需要人工切换,但是gevent能够自动切换任务。
其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
由于IO操作非常耗时,经常使程序处于等待的状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO
代码实现:
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 4)
g3 = gevent.spawn(f, 3)
g1.join() # join会等待g1标识的那个任务执行完毕之后对其进行清理工作,其实这就是一个耗时任务
g2.join()
g3.join()
运行结果:
从运行结果看,这里并没有进行任务的交替执行,是因为f()函数中没有耗时操作。
修改代码后:
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(1) # 如果用time.sleep(1)并不能实现多任务
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 4)
g3 = gevent.spawn(f, 3)
g1.join()
g2.join()
g3.join()
运行结果:
可以发现,任务是交替出现的。
如果非要用time.sleep(1)呢?修改代码如下:
from gevent import monkey
import time
import gevent
monkey.patch_all() # 可以把time.sleep(1)自动变成gevent.sleep(1)
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
# gevent.sleep(1)
time.sleep(1)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 4)
g3 = gevent.spawn(f, 3)
g1.join()
g2.join()
g3.join()
运行结果:
补充:
from gevent import monkey
import time
import gevent
monkey.patch_all()
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
# gevent.sleep(1)
time.sleep(1)
gevent.joinall([
gevent.spawn(f, 5),
gevent.spawn(f, 4),
gevent.spawn(f, 3)
])
小结:
①使用gevent来实现多任务的时候,有一个很特殊的地方,它可以自行切换协程指定的任务,而且切换的前提是:当一个任务用到耗时操作(例如延时),它就会把这个时间拿出去做另外的任务。这样做最终实现了多任务,而且自动切换。
②join会等待g1标识的那个任务执行完毕之后对其进行清理工作,其实这就是一个耗时任务
③函数里有gevent.sleep(1)有了等待之后,才能实现任务交替。只有time.sleep(1)不行
④如果要用time.sleep(1),需要monkey.patch_all()进行转换。monkey.patch_all()一定要放到使用time()等耗时操作的前面。