协程

时光总嘲笑我的痴心妄想 提交于 2020-01-15 04:08:31

一. 协程的概念

协程叫做微线程,它是python中实现多任务的方式之一。它比线程更小,占用更小的执行单元,它自带cpu上下文。

线程操作消耗性能,而协程的切换不那么消耗性能。

二. 简单案例

实现一个简单的协程:

使用yield关键字让函数变成生成器,再使用next关键字对其进行唤醒。

# -*- coding:utf-8 -*-
import time


def work1():
    while True:
        print("work")
        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()

结果:(循环)

work
work2
work
work2
......
......

三. 使用协程模块 - greenlet

安装:

pip install greenlet

实例:

# -*- coding:utf-8 -*-
import time
from greenlet import greenlet


def work1():
    while True:
        print("work1")
        # work1内切换到work2中去执行
        g2.switch()
        time.sleep(0.5)

def work2():
    while True:
        print("work2")
        g1.switch()
        time.sleep(0.5)


g1 = greenlet(work1)
g2 = greenlet(work2)

# 主程序切换到work1里面去执行
g1.switch()

结果:(循环)

work1
work2
work1
work2
......
......

这种切换占用系统资源很少,它实现多任务可以实现实现上万次操作。

四. 使用协程模块 - gevent

前面的greenlet模块,虽然功能很强大,但是在进行多任务时,实际上还是人工手动切换,那么就要引入一个更强大的模块gevent。
安装:

pip install gevent

原理:
IO的操作一般是比较耗时的,它总会使程序进入等待状态,例如:在进行读写操作时-请输入你的信息,程序就进入一个等待状态。

那么在进行一些耗时的操作时,在这个占用时间内,会自动切换到其他的协程内去执行,等到IO操作、读写操作完成以后,再适当的时间切换到原来的任务中去执行。

用gevent进行协程切换,只要遇到耗时操作需就进行切换,能保证总有协程在执行,而不是一直处于等待状态。

实例:

# -*- coding:utf-8 -*-
import gevent


def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # 模拟一个耗时操作,注意不是使用time模块中的sleep
        gevent.sleep(1)


g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

结果:

<Greenlet at 0x16367a5a948: f(5)> 0
<Greenlet at 0x16367a5ad48: f(5)> 0
<Greenlet at 0x16367a5ae48: f(5)> 0
<Greenlet at 0x16367a5a948: f(5)> 1
<Greenlet at 0x16367a5ad48: f(5)> 1
<Greenlet at 0x16367a5ae48: f(5)> 1
<Greenlet at 0x16367a5a948: f(5)> 2
<Greenlet at 0x16367a5ad48: f(5)> 2
<Greenlet at 0x16367a5ae48: f(5)> 2
<Greenlet at 0x16367a5a948: f(5)> 3
<Greenlet at 0x16367a5ad48: f(5)> 3
<Greenlet at 0x16367a5ae48: f(5)> 3
<Greenlet at 0x16367a5a948: f(5)> 4
<Greenlet at 0x16367a5ad48: f(5)> 4
<Greenlet at 0x16367a5ae48: f(5)> 4

三个不同协程交替执行

五. 给程序打补丁

猴子补丁,将原程序中的耗时操作代码进行替换,它改变了原代码所有有耗时操作的内容,具有一定风险性。这里将time.sleep打补丁为gevent.sleep

原程序:

# -*- coding:utf-8 -*-
import gevent
import random
import time

def work(name):
    for i in range(10):
        print(name, i)
        time.sleep(random.random())


gevent.joinall([
    gevent.spawn(work, 'work1'),
    gevent.spawn(work, 'work2')
])

原结果:

work1 0
work1 1
work1 2
work1 3
work1 4
work1 5
work1 6
work1 7
work1 8
work1 9
work2 0
work2 1
work2 2
work2 3
work2 4
work2 5
work2 6
work2 7
work2 8
work2 9

协程之间按顺序执行,不符合我们的预期结果,比较耗费资源。

打补丁后:

# -*- coding:utf-8 -*-
import gevent
import random
import time
# 加上猴子补丁
from gevent import monkey

# 有耗时操作时,替换原程序中的耗时代码
# 猴子补丁,将自己导入的模块进行替换,且改变代码块内容
# 这里改变了time模块,将time.sleep变成gevent.sleep
monkey.patch_all()


def work(name):
    for i in range(10):
        print(name, i)
        time.sleep(random.random())


gevent.joinall([
    gevent.spawn(work, 'work1'),
    gevent.spawn(work, 'work2')
])

结果:

work1 0
work2 0
work1 1
work2 1
work2 2
work1 2
work2 3
work2 4
work1 3
work2 5
work2 6
work2 7
work1 4
work1 5
work2 8
work1 6
work1 7
work2 9
work1 8
work1 9

实现自动切换,多个协程之间交替执行,一个协程阻塞就开启另一个协程执行。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!