Loading...
墨滴

HUIDBK

2021/03/29  阅读:31  主题:自定义主题1

Python协程

协程是啥

协程是 python 中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带 CPU 上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU 上下文那么程序还是可以运行的。

通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定


协程和线程差异

在实现多任务时,线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存 Cache 等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是 协程的切换只是单纯的操作 CPU 的上下文,所以一秒钟切换个上百万次系统都抗的住。


协程的实现

生成器 yield 关键字

import time


def task1():
    while True:
        print("----task1---")
        yield
        time.sleep(0.5)

        
def task2():
    while True:
        print("----task2---")
        yield
        time.sleep(0.5)

        
def main():
    w1 = task1()
    w2 = task2()
    
    while True:
        next(w1)
        next(w2)

        
if __name__ == "__main__":
    main()

运行结果:

----task1---
----task2---
----task1---
----task2---
----task1---
----task2---
----task1---
----task2---
----task1---
----task2---
----task1---
----task2---
...省略...

greenlet模块

为了更好使用协程来完成多任务,python 中的 greenlet 模块对其封装,从而使得切换任务变的更加简单

使用如下命令安装 greenlet 模块:

pip install greenlet

代码演示

import time
from greenlet import greenlet


def task1():
    while True:
        print("---A--")
        gr2.switch()
        time.sleep(0.5)

        
def task2():
    while True:
        print("---B--")
        gr1.switch()
        time.sleep(0.5)

        
gr1 = greenlet(task1)
gr2 = greenlet(task2)

#切换到gr1中运行
gr1.switch()

运行效果:

---A--
---B--
---A--
---B--
---A--
---B--
---A--
---B--
...省略...

gevent模块

greenlet 已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要着急,python还有一个比greenlet更强大的并且能够 自动切换任务 的模块 gevent

其原理是当一个 greenlet 遇到 IO操作(指的是input output 输入输出,比如网络、文件操作等)时,比如访问网络,就自动切换到其他的 greenlet,等到 IO操作 完成,再在适当的时候切换回来继续执行。

由于 IO操作 非常耗时,经常使程序处于等待状态,有了 gevent 为我们自动切换协程,就保证总有 greenlet在运行,而不是等待 IO

首先安装模块

pip install gevent

1. gevent的使用

import gevent


def fun(n):
    for i in range(n):
        print(gevent.getcurrent(), i)

        
g1 = gevent.spawn(fun, 5)
g2 = gevent.spawn(fun, 5)
g3 = gevent.spawn(fun, 5)

g1.join()
g2.join()
g3.join()

运行结果:

<Greenlet at 0x252186794c8: fun(5)> 0
<Greenlet at 0x252186794c8: fun(5)> 1
<Greenlet at 0x252186794c8: fun(5)> 2
<Greenlet at 0x252186794c8: fun(5)> 3
<Greenlet at 0x252186794c8: fun(5)> 4
<Greenlet at 0x25218679a68: fun(5)> 0
<Greenlet at 0x25218679a68: fun(5)> 1
<Greenlet at 0x25218679a68: fun(5)> 2
<Greenlet at 0x25218679a68: fun(5)> 3
<Greenlet at 0x25218679a68: fun(5)> 4
<Greenlet at 0x25218679b88: fun(5)> 0
<Greenlet at 0x25218679b88: fun(5)> 1
<Greenlet at 0x25218679b88: fun(5)> 2
<Greenlet at 0x25218679b88: fun(5)> 3
<Greenlet at 0x25218679b88: fun(5)> 4

可以看到,3个 greenlet 是依次运行而不是交替运行


2. gevent切换执行

import gevent


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

g1 = gevent.spawn(fun, 5)
g2 = gevent.spawn(fun, 5)
g3 = gevent.spawn(fun, 5)

g1.join()
g2.join()
g3.join()

运行结果:

<Greenlet at 0x162030284c8: fun(5)> 0
<Greenlet at 0x16203028a68: fun(5)> 0
<Greenlet at 0x16203028b88: fun(5)> 0
<Greenlet at 0x162030284c8: fun(5)> 1
<Greenlet at 0x16203028a68: fun(5)> 1
<Greenlet at 0x16203028b88: fun(5)> 1
<Greenlet at 0x162030284c8: fun(5)> 2
<Greenlet at 0x16203028a68: fun(5)> 2
<Greenlet at 0x16203028b88: fun(5)> 2
<Greenlet at 0x162030284c8: fun(5)> 3
<Greenlet at 0x16203028a68: fun(5)> 3
<Greenlet at 0x16203028b88: fun(5)> 3
<Greenlet at 0x162030284c8: fun(5)> 4
<Greenlet at 0x16203028a68: fun(5)> 4
<Greenlet at 0x16203028b88: fun(5)> 4

3. 给程序打补丁

import time
import gevent
import random
from gevent import monkey


def task(name):
    for i in range(5):
        print(name, i)
        time.sleep(random.random())

        
gevent.joinall([
        gevent.spawn(task, "task1"),
        gevent.spawn(task, "task2")
])

运行结果:

task1 0
task1 1
task1 2
task1 3
task1 4
task2 0
task2 1
task2 2
task2 3
task2 4

将程序中用到的耗时操作的代码, 换为 gevent 中自己实现的模块才会自行切换,看看打补丁后。

import time
import gevent
import random
from gevent import monkey


# 有耗时操作时需要
monkey.patch_all()


def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())

gevent.joinall([
        gevent.spawn(coroutine_work, "work1"),
        gevent.spawn(coroutine_work, "work2")
])

运行结果:

task1 0
task2 0
task2 1
task1 1
task2 2
task1 2
task1 3
task2 3
task1 4
task2 4

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。

HUIDBK

2021/03/29  阅读:31  主题:自定义主题1

作者介绍

HUIDBK