Python多任务-协程

协程,又称微线程
协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)
Python中的协程大概经历了如下三个阶段:

1、最初的生成器变形yield/send
2、yield from
3、在最近的Python3.5版本中引入async/await关键字

协程主要是通过运行代码中的时间延迟来完成任务,我们可以这样理解线程、进程、协程:
一条流水线上有很多工人进行生产工作,线程就是增加工人进行工作,进程就是添加流水线进行工作,协程就是一个流水线上的一个工人把他的工作做好了,然后进行其他的工作。

学习协程我们需要先了解下面几个概念:

同步、异步
同步:是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式
异步:是指代码调用IO操作时,不必等IO操作完成就返回的调用方式

阻塞、非阻塞
阻塞:从调用者的角度出发,如果在调用的时候,被卡住,不能再继续向下运行,需要等待,就说是阻塞
非阻塞:从调用者的角度出发, 如果在调用的时候,没有被卡住,能够继续向下运行,无需等待,就说是非阻塞

生成器-send方法

send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值

def create_num(num):
    a, b = 0, 1
    current_num = 0
    while current_num < num:
        result = yield a
        print("result-->", result)
        a, b = b, a+b
        current_num += 1


g = create_num(5)

print(next(g))
# 关闭生成器
# g.close()
# 如果前面不调用next 第一次必须发送None
print(g.send('haha'))

如上述代码的create_num是一个生成斐波那契数列的生成器,在这个代码中使用了send方法,第一个打印的内容时print(next(g))打印的0,第二个打印的内容是print(“result–>”, result)打印的result–>haha,第三个打印的内容为print(g.send(‘haha’))打印的1.
第一个打印出的0大家应该都能够理解,第二个打印出的值是因为send方法给yield a send了一个’haha’,所以result为’haha’,而g.send(‘haha’)取得还是生成器yield出得第二个值1。
其中如果第一次没有print(next(g)),直接使用send方法,则只能send(None)。

使用yield完成多任务

import time

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

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

def main():
    t1 = task1()
    t2 = task2()
    while True:
        next(t1)
        next(t2)

if __name__ == "__main__":
    main()

yield from介绍

在python3.3新加了yield from语法

lis = [1, 2, 3]


def generator_1(lis):
    yield lis


def generator_2(lis):
    yield from lis


if __name__ == '__main__':
    for i in generator_1(lis):
        print(i)

    for i in generator_2(lis):
        print(1)

上述代码的运行结果如下
在这里插入图片描述
generator_1的运行结果为一个列表,而generator_2的运行结果为列表中的每一个值,我们可以暂时粗略的将yield from理解为如果yield的对象是一个可迭代的,那就将其迭代一遍。

yield 案例:

"""子生成器"""
def generator_1():
    total = 0
    while True:
        x = yield
        print('加', x)
        if not x:       # if not None: True
            break
        total += x
    return total


"""委托生成器"""
def generator_2():
    while True:
        # 建立调用方和子生成器的通道
        total = yield from generator_1()  # 子生成器
        print('加和总数是:', total)


"""调用方"""
def main():
    # g1 = generator_1()
    # g1.send(None)
    # g1.send(2)
    # g1.send(3)
    # g1.send(None)
    g2 = generator_2()
    g2.send(None)
    g2.send(2)
    g2.send(3)
    g2.send(None)


if __name__ == '__main__':
    main()

子生成器:yield from后的generator_1()生成器函数是子生成器
委托生成器:generator_2()是程序中的委托生成器,它负责委托子生成器完成具体任务。
调用方:main()是程序中的调用方,负责调用委托生成器。

使用greenlet完成多任务

from greenlet import greenlet
import time


# 协程利用程序的IO 来切换任务
def sing():
    while True:
        print('I\'m singing')
        gr2.switch()
        time.sleep(0.5)


def dance():
    while True:
        print('I\'m dancing')
        gr1.switch()
        time.sleep(0.5)


if __name__ == '__main__':
    gr1 = greenlet(sing)
    gr2 = greenlet(dance)

    gr1.switch()

使用greenlet完成多任务需要在一个任务的运行中设置另一个任务的开始,即在sing中的gr2.switch()。

使用gevent完成多任务

import gevent
import time


# 协程利用程序的IO 来切换任务
def sing(n):
    for i in range(n):
        print('I\'m singing...%s' % i)
        time.sleep(0.5)


def dance(n):
    for i in range(n):
        print('I\'m dancing...%s' % i)
        time.sleep(0.5)


if __name__ == '__main__':
    g1 = gevent.spawn(sing, 5)
    g2 = gevent.spawn(dance, 5)

    g1.join()
    g2.join()

我们运行上述代码发现并没有实现多任务,而是sing运行结束了再开始的dance的运行。
这是因为在gevent中识别不了time的时间延迟,此时应该使用gevent中的时间延迟gevent.sleep().

import gevent


# 协程利用程序的IO 来切换任务
def sing(n):
    for i in range(n):
        print('I\'m singing...%s' % i)
        gevent.sleep(0.5)


def dance(n):
    for i in range(n):
        print('I\'m dancing...%s' % i)
        gevent.sleep(0.5)


if __name__ == '__main__':
    g1 = gevent.spawn(sing, 5)
    g2 = gevent.spawn(dance, 5)

    g1.join()
    g2.join()

或者可以在gevent中导入monkey,将代码改写如下:

import gevent
import time
from gevent import monkey
monkey.patch_all()


# 协程利用程序的IO 来切换任务
def sing(n):
    for i in range(n):
        print('I\'m singing...%s' % i)
        time.sleep(0.5)


def dance(n):
    for i in range(n):
        print('I\'m dancing...%s' % i)
        time.sleep(0.5)


if __name__ == '__main__':
    g1 = gevent.spawn(sing, 5)
    g2 = gevent.spawn(dance, 5)

    g1.join()
    g2.join()

monkey.patch_all()实现了将程序中用到的耗时操作 换为gevent中实现的功能。

同时也可将上述代码改写为joinall的形式:

import gevent
import time
from gevent import monkey
monkey.patch_all()


# 协程利用程序的IO 来切换任务
def sing(n):
    for i in range(n):
        print('I\'m singing...%s' % i)
        time.sleep(0.5)


def dance(n):
    for i in range(n):
        print('I\'m dancing...%s' % i)
        time.sleep(0.5)


if __name__ == '__main__':
    gevent.joinall([
        gevent.spawn(sing, 5),
        gevent.spawn(dance, 5)
    ])

线程进程协程简单总结

进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源很最大,效率很低
线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
协程切换任务资源很小,效率高
多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发

发布了24 篇原创文章 · 获赞 2 · 访问量 413

猜你喜欢

转载自blog.csdn.net/weixin_45735361/article/details/104056372