协程, 又称微线程, 纤程. 英文: Coroutine
协程是python中另外一种实现多任务的方式, 它比线程更小,占用更少执行单元. 协程自带 CPU 上下文. 这样只要在合适的时机, 我们可以把一个协程切换到另一个协程. 只要这个过程中保存或恢复 CPU 上下文那么程序还是可以运行的.
通俗的理解: 在一个线程中的某个函数, 可以在任何地方保存当前函数的一些零时变量等信息, 然后切换到另外一个函数中执行, 注意: 不是通过调用函数的方式做到的, 并且切换的次数以及什么时候再切换到原来的函数都由开发者自己决定
在实现多任务时, 线程切换从系统层面上讲远不止保存和恢复 CPU 上下文这么简单. 操作系统为了程序运行的高效性, 每个线程都有自己缓存 Cache等等数据, 操作系统还会进行恢复这些数据的操作. 所以线程的切换非常消耗性能. 但是协程的切换之时单纯的操作 CPU 上下文, 所以一秒钟切换上百万次操作系统都扛得住.
# –*– coding: utf-8 –*–
# @Time : 2019/3/30 20:44
# @Author : Damon_duanlei
# @FileName : coroutine_test01.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import time
def work_a():
while True:
print("---work_a---")
time.sleep(0.2)
yield
def work_b():
while True:
print("---work_b---")
time.sleep(0.2)
yield
def main():
g1 = work_a()
g2 = work_b()
while True:
next(g1)
next(g2)
if __name__ == '__main__':
main()
关于 yield 使用的详细内容请查看 python 迭代器生成器的相关文档,也可移步 https://blog.csdn.net/Damon_duanlei/article/details/88857702 此文可当做了解python协程的基础知识点.
为了更好使用协程来完成多任务, python中的greenlet模块对 yield 进行了封装, 从而使切换任务变的更加简单
pip install greenlet
# –*– coding: utf-8 –*–
# @Time : 2019/3/30 21:01
# @Author : Damon_duanlei
# @FileName : greenlet_test.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
from greenlet import greenlet
import time
def work_a():
while True:
print("---work_a---")
time.sleep(0.2)
g2.switch()
def work_b():
while True:
print("---work_b---")
time.sleep(0.2)
g1.switch()
if __name__ == '__main__':
g1 = greenlet(work_a)
g2 = greenlet(work_b)
# 切换到 g1 中运行
g1.switch()
注意: 使用 greenlet 协程间切换参数的传入使用 g.switch(*args, **kwargs)
greenlet 已经实现了协程, 但是还需要人工切换, python还有一个比 greenlet 更强大的并且能够自动切换任务的模块 gevent
其原理是当一个 greenlet 遇到 IO (指的是 input output 输入输出, 比如网络, 文件操作等) 操作时, 就自动切换到其他的 greenlet, 等到 IO 操作完成, 再在适当的时候切换回来继续执行.
由于 IO 操作非常耗时, 经常是程序处于等待状态, 有了 gevent 自动切换协程, 就保证总有 greenlet 在运行, 而不是等待 IO
# –*– coding: utf-8 –*–
# @Time : 2019/3/30 21:54
# @Author : Damon_duanlei
# @FileName : gevent_test.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import gevent
import time
def work(name):
for i in range(5):
print("{} do work {}".format(name, i))
time.sleep(0.2)
w1 = gevent.spawn(work, "臭臭")
w2 = gevent.spawn(work, "小迪")
w1.join()
w2.join()
运行结果:
臭臭 do work 0
臭臭 do work 1
臭臭 do work 2
臭臭 do work 3
臭臭 do work 4
小迪 do work 0
小迪 do work 1
小迪 do work 2
小迪 do work 3
小迪 do work 4
可以看到两个 greenlet 是依次运行的而不是交替运行, 因为genent 不会在遇到time.sleep 时进行协程间切换, 需要使用gevent.sleep时才能切换到其他的greenlet(其他的耗时操作同理). 使用gevent.sleep 对上面代码修改如下:
# –*– coding: utf-8 –*–
# @Time : 2019/3/30 21:54
# @Author : Damon_duanlei
# @FileName : gevent_test.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import gevent
import time
def work(name):
for i in range(5):
print("{} do work {}".format(name, i))
gevent.sleep(0.2)
w1 = gevent.spawn(work, "臭臭")
w2 = gevent.spawn(work, "小迪")
w1.join()
w2.join()
执行结果:
臭臭 do work 0
小迪 do work 0
臭臭 do work 1
小迪 do work 1
臭臭 do work 2
小迪 do work 2
臭臭 do work 3
小迪 do work 3
臭臭 do work 4
小迪 do work 4
如上面示例中, 若程序已经开发完成, 需要将已写好的耗时操作全部替换成 gevent 对应的耗时操作, 工作量是相当恐怖的. 所以就有了通过打补丁的方式将所有耗时操作换为gevent 中自己实现的模块.
# –*– coding: utf-8 –*–
# @Time : 2019/3/30 21:54
# @Author : Damon_duanlei
# @FileName : gevent_test.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import gevent
import time
from gevent import monkey
# 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
monkey.patch_all()
def work(name):
for i in range(5):
print("{} do work {}".format(name, i))
time.sleep(0.2)
w1 = gevent.spawn(work, "臭臭")
w2 = gevent.spawn(work, "小迪")
w1.join()
w2.join()
若一段程序中实例出大量的 gevent.spawn() 每一个都 join 也是非常痛苦的, 可以使用下方这段代码, 作为使用 gevent 实现协程的模板
# –*– coding: utf-8 –*–
# @Time : 2019/3/30 22:19
# @Author : Damon_duanlei
# @FileName : gevent_test01.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import gevent
import time
import random
from gevent import monkey
monkey.patch_all()
def fun(name):
for i in range(5):
print("{} : {}".format(name, i))
time.sleep(random.random())
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(fun, "臭臭"),
gevent.spawn(fun, "小迪"),
gevent.spawn(fun, "笨笨")
])
# –*– coding: utf-8 –*–
# @Time : 2019/3/30 22:40
# @Author : Damon_duanlei
# @FileName : gevent_test02.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import gevent
import time
import random
from gevent import monkey
monkey.patch_all()
def fun(name):
for i in range(3):
print("{} : {}".format(name, i))
time.sleep(random.random())
return "{} is OK !".format(name)
if __name__ == '__main__':
name_list = ["臭臭", "小迪", "笨笨"]
g_list = []
for name in name_list:
g = gevent.spawn(fun, name)
g_list.append(g)
gevent.joinall(g_list)
for i, g in enumerate(g_list):
print(g.value)
执行结果:
臭臭 : 0
小迪 : 0
笨笨 : 0
小迪 : 1
臭臭 : 1
臭臭 : 2
笨笨 : 1
小迪 : 2
笨笨 : 2
臭臭 is OK !
小迪 is OK !
笨笨 is OK !