当前位置: Python基础教程 > 17-多任务-协程 > 阅读正文

python的协程

2021.8.29.   384 次   2792字

协程, 不是线程, 也不是进程, 它也能实现多任务

但从技术本身来说, 它是”单进程, 单线程”的, 它是一种巧妙的函数封装技术

例如, 有一个奇数列表, 不断的取值是 1, 3, 5, 7… , 有一个偶数列表, 不断取值是 2, 4, 6, 8… 那么想要同时取值, 可以使用多线程或多进程来实现, 而协程也可以让它们看起来是”多任务”, 即取一个奇数, 取一个偶数

手动实现协程

协程, 并非操作系统实现的, 而是编程人员用代码实现的, 从操作系统上看并非多任务, 单从用户角度是多任务, 例如以下代码

gl_num1 = [1, 3, 5]
gl_num2 = [2, 4, 6]
current_a = 0
current_b = 0


def next_a():
    try:
        global current_a
        num1 = gl_num1[current_a]
        current_a += 1
        return num1
    except Exception as ex:
        return -1


def next_b():
    try:
        global current_b
        num2 = gl_num2[current_b]
        current_b += 1
        return num2
    except Exception as ex:
        return -1


def coroutine_a():
    """取a列表"""
    while True:
        num1 = next_a()
        if num1 == -1:
            break
        print(num1)


def coroutine_b():
    """取b列表"""
    while True:
        num2 = next_b()
        if num2 == -1:
            break
        print(num2)


def coroutine():
    """取一个a, 取一个b"""
    while True:
        a = next_a()
        if a == -1:
            break
        print(a)
        b = next_b()
        if b == -1:
            break
        print(b)


def main():
    # coroutine_a()   # 1, 3, 5
    # coroutine_b()   # 2, 4, 6
    coroutine()       # 1, 2, 3, 4, 5, 6


if __name__ == "__main__":
    main()

协程比线程, 进程消耗的更少, 在某些情况下是一个好选择

yield实现协程

显然, 这样的封装太依赖于技巧, 而python中, 通过前面学习的yield可以轻松实现

import time


def task_1():
    while True:
        print("---1---")
        time.sleep(1)
        yield


def task_2():
    while True:
        print("---2---")
        time.sleep(1)
        yield


def main():
    t1 = task_1()
    t2 = task_2()
    while True:
        next(t1)
        next(t2)


if __name__ == "__main__":
    main()

greenlet实现协程

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

安装模块

pip install greenlet

greenlet实例

from greenlet import greenlet
import time


def test1():
    while True:
        print("----A----")
        gr2.switch()
        time.sleep(1)


def test2():
    while True:
        print("----B----")
        gr1.switch()
        time.sleep(1)


gr1 = greenlet(test1)
gr2 = greenlet(test2)

# 开始执行, 切换到任务1
gr1.switch()

执行结果:

----A----
----B----
----A----
----B----
----A----
----B----
----A----
...

gevent实现协程

gevent是比greenlet更简单的协程实现方式

该模块同样需要安装, pip install gevent

简单案例

gevent实现协程的核心是, 利用sleep的时间, 去做其他事情, gevent中只有sleep才能实现多任务, 必须使用gevent.sleep()

import gevent


def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)     # sleep多久, 就是延迟多久, 只有sleep才会去做别的


def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)


def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)


g1 = gevent.spawn(f1, 5)  # 指定任务, 及参数
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
g1.join()    # 开始任务
g2.join()
g3.join()

程序执行结果:

<Greenlet at 0x193ee17f048: f1(5)> 0
<Greenlet at 0x193ee17f390: f2(5)> 0
<Greenlet at 0x193ee17f4a8: f3(5)> 0
<Greenlet at 0x193ee17f048: f1(5)> 1
<Greenlet at 0x193ee17f390: f2(5)> 1
<Greenlet at 0x193ee17f4a8: f3(5)> 1
<Greenlet at 0x193ee17f048: f1(5)> 2
<Greenlet at 0x193ee17f390: f2(5)> 2
<Greenlet at 0x193ee17f4a8: f3(5)> 2
<Greenlet at 0x193ee17f048: f1(5)> 3
<Greenlet at 0x193ee17f390: f2(5)> 3
<Greenlet at 0x193ee17f4a8: f3(5)> 3
<Greenlet at 0x193ee17f048: f1(5)> 4
<Greenlet at 0x193ee17f390: f2(5)> 4
<Greenlet at 0x193ee17f4a8: f3(5)> 4

Process finished with exit code 0

gevent.joinall() 参数是一个列表, 可以一次加入多个任务

对于现有的程序, time.sleep换成gevent.sleep并不是一个好的办法, 而是给程序”打补丁”

在打补丁模块的头部, 写入如下代码打补丁

from gevent import monkey
monkey.patch_all()

在某些下载器中, 同时下载多个任务时, 又不让服务器压力过大, 不给你多线程多进程, 而是协程方式

而在服务器多任务中中,通常每个进程就需要几mb内存,100并发则至少需要几百mb内存,若合理的使用协程做多线程,能承受巨大的并发量,因为它的消耗最少。

本篇完,还有疑问?

加入QQ交流群:11500065636 IT 技术交流群