0x00 前言

很久以前就听说 Python 的 async/await 很厉害,但是直到现在都没有用过,一直都在用多线程模型来解决各种问题。最近看到隔壁的 Go 又很火,所以决定花时间研究下 Python 协程相关的内容,终于在翻阅了一裤衩的资料之后有了一些理解。

0x01 起:一切从生成器开始

以往在 Python 开发中,如果需要进行并发编程,通常是使用多线程 / 多进程模型实现的。由于 GIL 的存在,多线程对于计算密集型的任务并不十分友好,而对于 IO 密集型任务,可以在等待 IO 的时候进行线程调度,让出 GIL,实现『假并发』。

当然对于 IO 密集型的任务另外一种选择就是协程,协程其实是运行在单个线程中的,避免了多线程模型中的线程上下文切换,减少了很大的开销。为了理解协程、async/await、asyncio,我们要从最古老的生成器开始。

回顾 Python 的历史,生成器这个概念第一次被提出的时候是在PEP 255中被提出的,当时的 Python 版本为 Python2.2。我们都知道range()函数,现在考虑一下我们来编写一个自己的range()函数,最直接最容易想到的方法也许是这样:

def my_range(max_number):

sequence = []

index = 0

while index < max_number:

sequence.append(index)

index += 1

return sequence

当你想创建一个很小的序列的时候,例如创建从 0 到 100 这样的列表,似乎没什么问题。但是如果想创建一个从 0 到 999999999 这么大的列表的话,就必须要创建一个完整的,长度是 999999999 的列表,这个行为非常占用内存。于是就有了生成器,用生成器来改写这个函数的话,会是下面这个样子:

def lazy_range(max_number):

index = 0

while index < max_number:

yield index

index += 1

当函数执行遇到yield的时候,会暂停执行。这样只需在内存中维护可以存储一个整数的内存空间就可以了。如果对生成器 / 迭代器不理解的话,可以参考 Stack Overflow 上的这篇高票回答:传送门

0x02 承:协程诞生

到这里可能还和协程没什么关系,但是实际上这已经是 Python 协程的雏形了,我们来看看维基上对于协程的定义:

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations.

从某些角度来理解,协程其实就是一个可以暂停执行的函数,并且可以恢复继续执行。那么yield已经可以暂停执行了,如果在暂停后有办法把一些 value 发回到暂停执行的函数中,那么 Python 就有了『协程』。于是在PEP 342中,添加了 “把东西发回已经暂停的生成器中” 的方法,这个方法就是send(),并且在 Python2.5 中得到了实现。利用这个特性我们继续改写range()函数:

def smart_range(max_number):

index = 0

while index < max_number:

jump = yield index

if jump is None:

jump = 1

index += jump

就这样,整个生成器的部分似乎已经进入了stable的状态,但是在 Python3.3 中,这个情况发生了改变。在PEP 380中,为 Python3.3 添加了yield from,这个东西可以让你从迭代器中返回任何值(这里用的是迭代器,因为生成器也是一种迭代器),也可以让你重构生成器,我们来看这个例子:

def lazy_range(max_number):

index = 0

def gratuitous_refactor():

while index < max_number:

yield index

index += 1

yield from gratuitous_refactor()

这个特性也可以让生成器进行串联,使数据在多个生成器中进行传递。历史发展到这里,协程的出现似乎已经就差一步了,或者这里说是异步编程更恰当。在 Python3.4 中加入了 asyncio 库,使 Python 获得了事件循环的特性(关于事件循环的内容这里不再赘述)。asyncio + 生成器已经达到了异步编程的条件,在 Python3.4 中,我们就可以这样实现一个异步的模型:

import asyncio

@asyncio.coroutine

def counttdown(number, n):

while n > 0:

print("T-minus", n, "({})".format(number))

yield from asyncio.sleep(1)

n -= 1

loop = asyncio.get_event_loop()

tasks = [

asyncio.ensure_future(counttdown("A", 2)),

asyncio.ensure_future(counttdown("B", 5)),

]

loop.run_until_complete(asyncio.wait(tasks))

loop.close()

这里的asyncio.coroutine装饰器是用来标记这个函数是一个协程的,因为asyncio要求所有要用作协程的生成器必须由asyncio.coroutine装饰。在这段代码中,时间循环会启动两个countdown()协程,他们会一直执行,直到遇到了yield from asyncio.sleep(),会暂停执行,并且将一个asyncio.Future对象返回给事件循环。事件循环会监控这个asyncio.Future对象,一旦其执行完成后,将会把这个 Future 的执行结果返回给刚刚因为这个 Future 暂停的协程,并且继续执行原协程。

从这个具体的例子出发,抽象点来看,实际上这个过程变成了:

你可以对任何asyncio.Future的对象进行yield from,将这个 Future 对象交给事件循环;

暂停执行的协程将等待这个 Future 的完成;

一旦 Future 获取到事件循环,并执行完所有的代码;

事件循环感知到 Future 执行完毕,原暂停的协程会通过 send() 方法获取 Future 对象的返回值并且继续执行;

0x03 转:从 yield from 到 await

终于到了最激动人心的地方,在 Python3.5 中,添加了types.coroutine装饰器以及async def和await。我们先来看一下 Python3.4 和 Python3.5 中如何定义一个协程函数:

# python34

@asyncio.coroutine

def py34_function():

yield from work()

# python35

async def py35_function():

await work()

看起来 Python3.5 中定义协程更为简单了,但是实际上生成器和协程之间的差别变的更加明显了。这里先要指出两个个注意点:

await 只能用于 async def 的函数中;

yield from 不能用于 async def 的函数中;

除此之外,yield from和await可以接受的对象是略有区别的,await接受的对象必须是一个awaitable对象。什么是awaitable对象呢,就是一个实现了__await()__方法的对象,而且这个方法必须返回一个不是协程的迭代器。满足这两个条件,才算是一个awaitable对象,当然协程本身也是awaitable对象,因为collections.abc.Coroutine继承了collections.abc.Awaitable。换句话说,await后面可接受的对象有两种,分别是:协程和awaitable对象,当然协程也是awaitable对象。

在 Python3.6 中,这种特性继续被发扬光大,现在可以在同一个函数体内使用yield和await,而且除此之外,也可以在列表推导等地方使用async for或await语法。

result = [i async for i in aiter() if i % 2]

result = [await func() for fun in funcs if await condition()]

async def test(x, y):

for i in range(y):

yield i

await asyncio.sleep(x)

0x04 合:尾声

到这里整个协程的历史已经是回顾完了,对于 Python 中的协程也有了一些理解,但是如何在实际中使用协程可能还有一些疑惑以及理解不够深刻的地方,准备继续研究几天,在下一篇文章讲一下实际场景中协程的运用和项目中遇到的问题。最后附上一些不错的文献:D

python从零开始到放弃_Python 协程从零开始到放弃相关推荐

  1. python gevent模块 下载_Python协程阻塞IO非阻塞IO同步IO异步IO

    Python-协程-阻塞IO-非阻塞IO-同步IO-异步IO 一.协程 协程又称为微线程 CPU 是无法识别协程的,只能识别是线程,协程是由开发人员自己控制的.协程可以在单线程下实现并发的效果(实际计 ...

  2. python协成_Python协程技术的演进

    引言 1.1. 存储器山 存储器山是 Randal Bryant 在<深入理解计算机系统>一书中提出的概念. 基于成本.效率的考量,计算机存储器被设计成多级金字塔结构,塔顶是速度最快.成本 ...

  3. python 协程库_python --- 协程编程(第三方库gevent的使用)

    1. 什么是协程? 协程(coroutine),又称微线程.协程不是线程也不是进程,它的上下文关系切换不是由CPU控制,一个协程由当前任务切换到其他任务由当前任务来控制.一个线程可以包含多个协程,对于 ...

  4. python协程和线程区别_python 协程 及其与python多线程的区别和联系

    协程(coroutine)又称微线程,纤程,是种用户级别的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时候,将寄存器上下文和栈保存到其他地方,等待切换回来的时候恢复,并从之前保存的寄存器 ...

  5. Python 线程和进程和协程总结

    Python 线程和进程和协程总结 线程和进程和协程 进程 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间.内存等)的基本单位: 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其 ...

  6. 线程 协程 python_在Python 的线程中运行协程的方法

    在一篇文章 理解Python异步编程的基本原理 这篇文章中,我们讲到,如果在异步代码里面又包含了一段非常耗时的同步代码,异步代码就会被卡住. 那么有没有办法让同步代码与异步代码看起来也是同时运行的呢? ...

  7. python正在处理中_协程和 asyncio

    在本系列的 第 1 部分 中,您了解了 Python 迭代器:在 第 2 部分 中,您了解了 itertools.在这一部分,将了解一种称为协程(Coroutines)的特殊的生成器函数.您还将了解另 ...

  8. python 进程,线程,协程篇

    python 进程,线程,协程篇 ssh 线程 进程 线程,进程区别 threading 模块,两种调用方式 python GIL全局解释器锁(Global Interpreter Lock) Joi ...

  9. python 协程_Python 协程与 Go 协程的区别(一)

    ? "Python猫" ,一个值得加星标的公众号 花下猫语:年关将近,不知各位过得怎样?我最近有些忙,收获也挺多,以后有机会分享下.吃饭时间,追了两部剧<了不起的麦瑟尔夫人& ...

最新文章

  1. linux服务 ssh
  2. Spring Boot CMI 使用笔记
  3. 妙用通配符证书发布多个安全站点
  4. 当女朋友问你会不会出轨的时候,该怎么回答?
  5. JQuery Datatables editor进行增删改查操作(一)
  6. centos mysql 主从_CentOS 搭建 MySql 主从备份
  7. chrome自定义背景_Android Chrome自定义标签
  8. 单片机p2.0引脚c语言,单片机p2.0?
  9. 光谱共焦位移传感器原理和特性
  10. uint和int的区别
  11. 在线banner制作网站
  12. 数值微分25 - Poisson 泊松方程(一维、二维):已知一些自定义的二阶导数点,得到一条曲线,即原方程平滑地通过它们)
  13. 【SAP】 SAP自定义权限对象
  14. iPhone的来电铃声
  15. 第4篇 Fast AI深度学习课程——深度学习在回归预测、NLP等领域的应用
  16. 商务工作人员用什么邮箱,超好用的商务邮箱申请攻略!
  17. Python 中隐藏的彩蛋们
  18. 设置firefox背景为黑夜模式
  19. VS2022换主题和背景
  20. 利用keil完成ARM汇编语言编程入门实践

热门文章

  1. 【Elasticsearch】Elasticsearch 导入 导出 命令行工具
  2. 【Docker】Docker安装telnet
  3. 【Linux】Linux 守护进程的启动方法
  4. 【OSGI】Error osgi xx Invalid value for DynamicImport-Package dynamic.import.pack
  5. 【mac】Mac 安装Brew命令
  6. Kudu报错:你的主机中的软件终止了一个已建立的链接
  7. 12-linux安装kibana
  8. 没学会python怎么学_恕我直言!这么久还没学会python,那是因为你还没有看过这本书...
  9. 使用Redis作为分布式锁的错误用法
  10. 用Java开发一个停车场系统