协程(Coroutine)是由程序员在代码中显示调度的。(进程和线程是由操作系统调度,线程是操作系统调度的最小单位)。
看过前边的文章应该知道,线程任务在 IO 阻塞之后,操作系统会进行线程切换,这个切换会浪费时间与资源。而协程是在单个线程中并发的多个任务,当执行中的协程任务遇到阻塞之后,立即切换到其他就绪状态协程任务去执行,这样会极大的减小了线程因为阻塞而有操作系统切换。协程的切换属于程序级别,非常迅速,上线文完全有程序员在代码中显示管理,切换速度极快,所以减少了线程切换浪费资源。
协程的优点:
协程的缺点:
yield 将一个函数构造为生成器。使用 yield 可以做协程的简单实现。当在任务中调用生成器的时,当前执行任务会切换到生成器来获取数据,获取到数据之后再切回之前的执行任务。
和 yield 相关的有两个常用方法:
def consumer():
while True:
print('马上执行到yield')
x = yield
print('处理了数据:', x)
if __name__ == "__main__":
c = consumer()
next(c) # 第一次执行到 yield
next(c) # 执行 yield 之后,直到下一次遇到 yield
c.send(1) # 向 yield 发送1,向后执行直到下一次遇到 yield
c.send(2) # 向 yield 发送2,向后执行直到下一次遇到 yield
运行结果:
马上执行到yield
处理了数据: None
马上执行到yield
处理了数据: 1
马上执行到yield
处理了数据: 2
马上执行到yield
当然出了以上的用法 还有 yield 正常一点的生成器用法:
def consumer():
for i in range(6):
yield i
if __name__ == "__main__":
for i in consumer():
print(i**2)
# 或者使用 next
# 生成器不可以二次使用
c = consumer()
print(next(c))
print(next(c))
print(next(c))
# 生成器中已经使用完毕,下一个报错了
print(next(c))
运行结果:
0
1
4
0
1
2
Traceback (most recent call last):
File “markdown/test.py”, line 20, in
print(next©)
StopIteration
greenlet 提供了 python 中协程实现的一种。最外层是调用的初始任务,内层任务是各个 greenlet。可以创建协程并在它们之间跳转。
在 greenlet 协程中跳转都是显式的:一个 greenlet(叫它g1)选择跳转到另一个 greenlet(叫它g2),这将导致 g1 暂停,而 g2 开始执行一直到遇到切换或执行完毕,当执行完毕时会自动切换到 g1,g1 继续执行(切换走的时候又状态保存)。
在 greenlet 中切换到协程需要使用 switch 方法,当对 greenlet 第一次执行 greenlet.switch() 时,需要传递参数,在后续的 switch 中不用传参(传了也无效)
from greenlet import greenlet
# 协程切换之 吃完睡 睡醒吃 吃完再睡
def eat(user):
print('%s eat apple' % user)
g2.switch('Dolphin') # 3
print('%s eat banane' % user)
g2.switch()
def sleep(user):
print('%s is sleeping 1' % user)
g1.switch("Jary")
print('%s is sleeping 2' % user)
if __name__ == "__main__":
g1 = greenlet(eat)
g2 = greenlet(sleep)
g1.switch('Dolphin')
输出结果:
Dolphin eat apple
Dolphin is sleeping 1
Dolphin eat banane
Dolphin is sleeping 2
greenlet 相比较 generator 提供了 switch 更方便切换协程,但是所有的协程都由我们手动调度还是不够友好。如果能在协程之间遇到阻塞自动调度,那可就太好了~
于是,Gevent 乘着七彩祥云来了~
Gevent 是一个包装了 greenlet 的第三方库,他使用起来更像 threading 那样可以协程也实现自动调度,可以轻松通过 gevent 实现并发同步或异步编程。
使用 gevent.spawn 启动协程不同于 greenlet 的手动切换。使用 gevent.spawn 创建的协程,会进入就绪状态等待执行。
对于已经常见的协程,可能主线程已经结束其还未执行完毕,可以在主线程中使用 join 等待期执行完毕,或者使用 gevent.joinall() 等待多个协程 。
在 gevent 协程中,默认只认 gevent 定义的阻塞(例如:gevent.sleep())。像 time.sleep()、open()、网络请求这种阻塞会被忽略。可以通过打补丁来解决:
from gevent import monkey
# 在其之后的 time.sleep()、open()、网络请求 都被 gevent 认为会阻塞
monkey.patch_all()
# 请把这种方式当成一种习惯
from gevent import monkey;monkey.patch_all()
https://www.cnblogs.com/russellyoung/p/python-zhi-xie-cheng.html