回忆进程与线程 引入
我们之前说,有比较耗时的操作得交给线程来干,因为进程是同步调用,阻塞执行,串行执行的,那么我们就要用线程,异步调用,非阻塞,并行执行,这样的话才能提升效率。
问题来了,假设线程中也有几个子任务,耗时,然后我们程序都是串行的,也就一个Thread里面的run()
都是串行执行的,一个子任务,比方下载很慢,其他任务都阻塞在那,就效率又很低了。
这时的线程就好像以前的进程一样尴尬。
我们python就出来一个微线程,可以并行操作的,线程的子任务部分,也称为
携程
协程(Coroutine) cor - routine
也可以看出并行的含义
比如,我们有很多任务要做,打游戏,学习python,配朋友,这就是三个进程。在打游戏中,我有 黑暗之魂1,2,3,上古卷轴5,也就是4个线程,当然,上古卷轴5有100个mod,我需要更新100个mod,就有了100个协程。因为更新比较耗时,必须并行调用 更新mod的任务。
greenlet
python封装了库greenlet来实现协程,我们来看个例子:
#-*- utf-8 -*-
from time import sleep,time
from greenlet import greenlet
def taskA():
for i in range(5):
print('A'+str(i))
gB.switch()
sleep(1) # 模拟网络阻塞 耗时操作
def taskB():
for i in range(5):
print('B' + str(i))
gC.switch()
sleep(1) # 模拟网络阻塞 耗时操作
def taskC():
for i in range(5):
print('C' + str(i))
gA.switch()
sleep(1) # 模拟网络阻塞 耗时操作
if __name__ == "__main__":
t1 = time()
gA = greenlet(run = taskA)
gB = greenlet(run = taskB)
gC = greenlet(run = taskC)
gA.switch()
t2 = time()
print("time consume",t2-t1)
类似process thread, greenlet也必须传target参数(他命名为run参数 反正一个意思)
这里主要是实现A B C并行轮询,但是我们可以看到这样写很蠢,需要人工切换,而且效率也不算高,于是官方的greenlet不好用,就有大佬弄出来gevent库(第三方),在greenlet基础上再封装一波,可以实现自动切换。
gevent
利用spawn函数传要做的事(taskA B C),spawn有 产卵; 引发; 引起 这几个意思,有点run的含义。
注意,我们之前在进程里面遇到过,主进程启动完子进程start以后就跑路了,导致子进程也gg,这样我们就用join阻塞,让主进程别溜,这里也是同样的道理。
#-*- utf-8 -*-
from time import sleep,time
from gevent import monkey,spawn
def taskA():
for i in range(3):
print('A'+str(i))
sleep(1) # 模拟网络阻塞 耗时操作
def taskB():
for i in range(3):
print('B' + str(i))
sleep(1) # 模拟网络阻塞 耗时操作
def taskC():
for i in range(3):
print('C' + str(i))
sleep(1) # 模拟网络阻塞 耗时操作
if __name__ == "__main__":
t1 = time()
gA = spawn(run = taskA)
gB = spawn(run = taskB)
gC = spawn(run = taskC)
gA.join()
gB.join()
gC.join()
t2 = time()
print("time consume",t2-t1)
结果:
A0
A1
A2
B0
B1
B2
C0
C1
C2
time consume 9.041131258010864
我们发现,
1: 并没有轮询ABC ABC 这样的啊
2: 并没有并行啊2333 其实上面的greenlet也没有实现并行??? 还是9s
monkey
我们的time不适合这个场合的,于是利用monkey.patch_all()
#-*- utf-8 -*-
import time
from gevent import monkey,spawn
monkey.patch_all()
def taskA():
for i in range(3):
print('A'+str(i))
time.sleep(1) # 模拟网络阻塞 耗时操作
def taskB():
for i in range(3):
print('B' + str(i))
time.sleep(1) # 模拟网络阻塞 耗时操作
def taskC():
for i in range(3):
print('C' + str(i))
time.sleep(1) # 模拟网络阻塞 耗时操作
if __name__ == "__main__":
t1 = time.time()
gA = spawn(run = taskA)
gB = spawn(run = taskB)
gC = spawn(run = taskC)
gA.join()
gB.join()
gC.join()
t2 = time.time()
print("time consume",t2-t1)
这里 必须time.sleep才能生效 因为monkey在背后偷偷重载替换了原生的sleep,你直接from time import sleep, monkey匹配不到
结果:
A0
B0
C0
A1
B1
C1
A2
B2
C2
time consume 3.036893606185913
Process finished with exit code 0
可见,比较完美实现了并行,time consume 时间消耗 从9s 并行变成了 3s
总结
我们直接用gevent monkey就行了 其他的执行方式太麻烦了,简直就是再造轮子:)