一.什么是协程:
协程是python中另外一种实现多任务的方式,只不过比线程更小,占用的执行资源(理解为需要的资源)也更少。为啥说他说一个执行单元?因为他自带cpu上下文,这样,在合适的额gr时机,我们就可以把一个协程切换到另一个协程。只要这个过程中保存或恢复了cpu上下文,那么程序就还是可以运行的。
二.协程与线程的差异:
在实现多任务时,线程的切换远不止系统层面上的保存和恢复cpu上下文那么简单。操作系统为了程序运行的高效性,每个线程都有自己的缓存cache等数据,操作系统会帮你做这些数据的恢复操作(快照恢复),所以线程的切换相对协程而言更耗性能。但是协程的切换只是单纯地操作cpu上下文,所以一秒钟切换上百万次协程都是没问题的。
python中的高性能框架tornado和性能测试工具locust底层用的就是协程实现。
1.未通过生成器实现多任务:yield
"""未通过yield关键字利用生成器实现多任务"""
import time
def work1():
for i in range(5):
print("-----work1--执行----{}".format(i))
time.sleep(0.1)
def work2():
for i in range(5):
print("-----work2--执行----{}".format(i))
time.sleep(0.1)
if __name__ == '__main__':
st = time.time()
work1()
work2()
et = time.time()
print("运行时间为:{}秒".format(et - st))
输出:
-----work1--执行----0
-----work1--执行----1
-----work1--执行----2
-----work1--执行----3
-----work1--执行----4
-----work2--执行----0
-----work2--执行----1
-----work2--执行----2
-----work2--执行----3
-----work2--执行----4
运行时间为:1.0294520854949951秒
总结:未实现生成器之前,程序运行按顺序执行:work1执行完了再执行work2。
2.通过生成器实现微线程(协程):yield(保存执行的上下文)
通过yield生成器实现多任务时,work1和work2时交替执行的。
"""通过yield关键字利用生成器实现多任务"""
import time
def work1():
for i in range(5):
print("-----work1--执行----{}".format(i))
yield
time.sleep(0.1)
def work2():
for i in range(5):
print("-----work2--执行----{}".format(i))
yield
time.sleep(0.1)
if __name__ == '__main__':
st = time.time()
g1 = work1()
g2 = work2()
while True:
try:
next(g1)
next(g2)
except Exception as e:
print("无可迭代对象")
break
et = time.time()
print("运行时间:{}".format(et - st)) # 4.696846008300781e-05
输出:
-----work1--执行----0
-----work2--执行----0
-----work1--执行----1
-----work2--执行----1
-----work1--执行----2
-----work2--执行----2
-----work1--执行----3
-----work2--执行----3
-----work1--执行----4
-----work2--执行----4
无可迭代对象
运行时间:0.9351921081542969
总结: 1.协程(微线程,Coroutine)的本质是单任务;
2.协程依赖于线程;
3.协程相对于线程,占用的资源更少(几乎不占用资源)。
三.greenlet(比较low):
为了更好的使用协程来实现多任务,python中的greenlet模块对其封装,从而使得切换任务变得更加简单。
"""
协程模块安装:pip install greenlet/pip install gevent
"""
from greenlet import greenlet
import time
def work1():
for i in range(5):
print("work1--正在执行:{}".format(i))
g2.switch() # 切换到g1
time.sleep(0.1)
def work2():
for i in range(5):
print("work2--正在执行:{}".format(i))
g1.switch() # 切换到g2
time.sleep(0.1)
if __name__ == '__main__':
st = time.time()
g1 = greenlet(work1)
g2 = greenlet(work2)
# 切换到g1中执行
g1.switch() # 执行g1
print("所有协程执行完毕~~~")
et = time.time()
print("运行时间:{}".format(et - st))
输出:
work1--正在执行:0
work2--正在执行:0
work1--正在执行:1
work2--正在执行:1
work1--正在执行:2
work2--正在执行:2
work1--正在执行:3
work2--正在执行:3
work1--正在执行:4
work2--正在执行:4
所有协程执行完毕~~~
运行时间:0.930009126663208
四.gevent:(推荐)
协程存在与线程之中,线程默认不会等待协程执行。 spawn():开启协程(第一个参数为协程要执行的任务) join():线程要等待协程执行结束。 协程之间切换条件: gevent.sleep():耗时等待的情况下才会切换
"""
协程:gevent(终极版本)
协程存在与线程之中,线程默认不会等待协程执行。
spawn():开启协程(第一个参数为协程要执行的任务)
join():线程要等待协程执行结束。
协程之间切换条件:
gevent.sleep():耗时等待的情况下才会切换
"""
import gevent, time
def work1():
for i in range(5):
print("work1--正在执行:{}".format(i))
gevent.sleep(0.1)
def work2():
for i in range(5):
print("work2--正在执行:{}".format(i))
gevent.sleep(0.1)
if __name__ == '__main__':
st = time.time()
# 创建2个协程
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
# 线程等待协程执行完毕
g1.join()
g2.join()
et = time.time()
print("运行时间为:{}秒".format(et - st))
输出:
work1--正在执行:0
work2--正在执行:0
work1--正在执行:1
work2--正在执行:1
work1--正在执行:2
work2--正在执行:2
work1--正在执行:3
work2--正在执行:3
work1--正在执行:4
work2--正在执行:4
运行时间为:0.5262598991394043秒
使用gevent.monkey.patch_all()补丁,可不用gevent.sleep()方法,有耗时的io操作时会自动切换。
"""
协程:gevent(终极版本)
协程存在与线程之中,线程默认不会等待协程执行。
spawn():开启协程(第一个参数为协程要执行的任务)
join():线程要等待协程执行结束。
协程之间切换条件:
gevent.sleep():耗时等待的情况下才会切换
如果使用了gevent的程序补丁,则不需要gevent.sleep(),会自动切换
gevent.monkey.patch_all()
"""
import gevent, time
from gevent import monkey
gevent.monkey.patch_all()
def work1():
for i in range(5):
print("work1--正在执行:{}".format(i))
time.sleep(0.1)
def work2():
for i in range(5):
print("work2--正在执行:{}".format(i))
time.sleep(0.1)
if __name__ == '__main__':
st = time.time()
# 创建2个协程
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
# 线程等待协程执行完毕
g1.join()
g2.join()
et = time.time()
print("运行时间为:{}秒".format(et - st))
输出:
work1--正在执行:0
work2--正在执行:0
work1--正在执行:1
work2--正在执行:1
work1--正在执行:2
work2--正在执行:2
work1--正在执行:3
work2--正在执行:3
work1--正在执行:4
work2--正在执行:4
运行时间为:0.518481969833374秒