python-协程


从生成器到协程

从Python2到Python3,协程经历了翻天覆地的变化。

简单的生成器

https://www.python.org/dev/peps/pep-0255/
Python2.3中,加入了新的关键字yield
在PEP255中,引入了yield表达式
规定yield语句只能在函数中使用。包含yield语句的函数被称为生成器函数。
当执行到yield语句是,函数的状态会被冻结(挂起所有状态,如:局部变量、指令指针、堆栈状态等),以便下载调用next时恢复状态继续执行。

通过生成器实现协程

协程的底层架构是在 PEP 342 中定义,在 Python2.5 实现的。
实现思想:使用yield挂起生成器,使用send 方法激活生成器。这样生成器就具备实现协程的功能。
send方法类似于next,不同的是send方法可以修改值,next不能修改只能取下一个。
执行 generator.send(None) 完全等同于调用生成器的 next 方法。使用其它参数调用 send 也有同样的效果,不同的是,当前生成器表达式产生的值会不一样。

参考资料:PEP 342 Coroutines via Enhanced Generators

协程的演变

Python2 中生成器中 return 语句提供值,会抛出 SyntaxError 异常。
Python3 中生成器可以 return 一个值,
Python3.3 增加了 yield from 语法,使调用嵌套生成器变得简单
Python3.5 加入了关键字 async 和 await ,将生成器和协程彻底分开。

参考资料:
PEP 380 Syntax for Delegating to a Subgenerator
PEP 525 Asynchronous Generators

生成器原理

函数是如何执行的

Python 解释器是基于栈的,其中有三种栈:调用栈 (frame stack)、数据栈 (data stack)、块栈 (block statck)。

对于Python编译器来说,PyCodeObject对象是其真正的编译结果。

在代码运行时,字节码会存储在 PyCodeObject 对象中。PyCodeObject 保存的是编译后的静态信息,在运行的时候会结合上下文形成一个完整的运行态环境。函数的__code__变量其实就是函数的编译后的PyCodeObject对象

生成器是如何执行的

YIELD_VALUE 指令,挂起当前函数
调用生成器函数时,生成器不会立即执行,而是返回一个生成器对象。生成器对象有 gi_code 和 gi_frame 两个常用的属性,gi_code 和 code 一样存放了字节码,gi_frame 中存储了运行时的数据。
使用 send 方法也可以使生成器继续执行,和 next 不同的是,send 方法可以向生
成器传入值
yield 之后和之前的代码执行是分离的,函数在遇到 yield 会交出控制权,send
方法会重新获取控制权。

使用生成器实现多任务

原理:函数遇到yield挂起,就会去执行别的任务。
缺点:虽然实现了并发,但是不能检测IO阻塞(例如sleep)。

import time

def calc_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f'执行时间是:{end_time - start_time}')
        return result
    return wrapper

def work1():
    for i in range(5):
        print(f'听音乐....{i}')
        time.sleep(1)
        yield

def work2():
    for i in range(5):
        time.sleep(1)
        print(f'打游戏....{i}')
        yield

@calc_time
def main():
    g1 = work1()
    g2 = work2()
    while True:
        try:
            next(g1)
            next(g2)
        except StopIteration: # 没有下一个next取不到数据的时候报这个异常
            break

main()

gevent实现异步

gevent 是应用非常广泛的异步网络库,底层使用 greenlet 协程
gevent能自动检测io阻塞,遇到阻塞自动切换任务。

import time
import gevent
from gevent import monkey # gevent的补丁,为了解决gevent遇到time.sleep不切换的问题

monkey.patch_all() # 使用gevent的补丁

def calc_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f'执行时间是:{end_time - start_time}')
        return result
    return wrapper

def work1():
    for i in range(5):
        print(f'听音乐....{i}')
        # time.sleep(1)
        gevent.sleep(60*5)

def work2():
    for i in range(5):
        time.sleep(1)  # gevent能自动检测io阻塞,遇到阻塞自动切换任务
        print(f'打游戏....{i}')

@calc_time
def main():
    g1 = gevent.spawn(work1)  # 创建协程1
    g2 = gevent.spawn(work2)  # 创建协程2
    g1.join()
    g2.join()

if __name__ == '__main__':
    main()

协程非常安全,遇到阻塞自动切换。

协程核心思想

为了实现异步IO
协程是为了保证线程安全设计的。
若干个协程任务,当某个任务遇到阻塞时,自动切换到非阻塞的任务上。

阻塞: IO阻塞。 如:

  • input(“请输入一个数:”)
  • 磁盘IO(写数据是需要时间的)
  • 网络IO(网络请求数据是需要时间的)
  • time.sleep(2)

asyncio实现异步

import time
import asyncio

def calc_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f'执行时间是:{end_time - start_time}')
        return result
    return wrapper

async def work1(): # 每一个方法钱都要加async标识
    for i in range(5):
        await asyncio.sleep(1) # 需要耗时的任务前加await,自动切换到其他任务
        print(f'听音乐....{i}')

async def work2():
    for i in range(5):
        await asyncio.sleep(1)
        print(f'打游戏....{i}')

async def main():
    task1 = asyncio.create_task(work1()) # 创建协程任务
    task2 = asyncio.create_task(work2())
    await task1 # 启动协程
    await task2

if __name__ == '__main__':
    asyncio.run(main())

用户态 : 切换不需要CPU调度
核心态: 线程切换,进程切换,核心态 , 切换需要CPU调度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值