惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Privacy International News Feed
T
Threatpost
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
NISL@THU
NISL@THU
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
Simon Willison's Weblog
Simon Willison's Weblog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Jina AI
Jina AI
雷峰网
雷峰网
Know Your Adversary
Know Your Adversary
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
Intezer
博客园 - Franky
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
The Hacker News
The Hacker News
K
Kaspersky official blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tailwind CSS Blog
Project Zero
Project Zero
T
Tor Project blog
B
Blog RSS Feed
Recorded Future
Recorded Future
Scott Helme
Scott Helme
美团技术团队
V
V2EX
V
Visual Studio Blog
L
Lohrmann on Cybersecurity
P
Proofpoint News Feed
D
DataBreaches.Net
The Register - Security
The Register - Security
M
MIT News - Artificial intelligence
L
LangChain Blog
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园_首页
P
Privacy & Cybersecurity Law Blog

Asyncio - 标签 - This Cute World

暂无文章

Python 异步编程笔记:asyncio
於清樂 · 2019-02-14 · via Asyncio - 标签 - This Cute World

个人笔记,不保证正确。

虽然说看到很多人不看好 asyncio,但是这个东西还是必须学的。。基于协程的异步,在很多语言中都有,学会了 Python 的,就一通百通。

Python 的 asyncio 是通过 generator 实现的,要学习 async,先得复习下 generator.

众所周知,yield 是用于定义 generator 函数的关键字,调用该函数,会返回一个 generator

>>> def f():
...     yield 1
...     yield 2
...
>>> f()  # 返回的是 generator
<generator object f at 0x7f672c460570>
>>> g = f()
>>> next(g)  # 通过 next 方法从 generator 获取值
1
>>> g.__next__()  # next 方法实际是调用了 generator 的 __next__ 方法
2
>>> next(g)  # 生成器运行结束,产生一个 StopIteration 的 exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

每次调用 next,generator 都只会运行到下一个 yield 关键字所在行,返回 yield 右侧的对象,然后暂停在该处,等待下一次 next 调用。

从上面的例子看,yield 就是延迟求值而已。但是 yield 还有一个特性,就是它是一个 expression,有返回值!看例子:

>>> def func():
...     r = yield 1
...     yield r
...
>>> g = func()
>>> next(g)
1
>>> next(g)  # 通过 next 调用,yield 的返回值为 None
>>> g2 = func()
>>> next(g2)  # 首先需要通过 next 调用,运行到 yield 语句处
1
>>> g2.send(419)  # 现在用 send 方法,这会将当前所在的 yield 语句的值设置为你 send 的值,也就是 419
419  # 然后 generator 运行到下一个 yield,返回右边的值并暂停

generator 有四个实例函数:next、send 是刚刚已经介绍了的,此外还有 throw 用于从 yield 所在处抛出 Exception,和 close 用于关闭 Generator。详见Generator-iterator methods

可以理解成是 yield <value> from <iterable>,每次调用时它都会从 <iterable> 中取值,直到遇到 StopIteration。才会从下一个 yield 取值。

>>> def f():
...     yield from [1, 2, 3, 4]  # iterable
...     yield 5
...     yield from range(4, 0, -1)  # iterable
...
>>> list(f())
[1, 2, 3, 4, 5, 4, 3, 2, 1]

当然,yield from <iterable> 也是一个 expression,也有值。它的值就是 StopIteration 异常的第一个参数,内置类型的这个值都是 None.

>>> def f():
...     r = yield from [1, 2]
...     yield f"value of yield from is {r}"
...
>>> list(f())
[1, 2, 'value of yield from is None']

当 <iterable> 是 generator 时,yield from 会直接将函数调用委托给这个子 generator,这里的调用包括了前面说过的 next、send、throw、close 四个函数。并直接将 sub generator yield 的值 yield 给 caller.

generator 中的 return value,语义上等同于 raise StopIteration(value)

>>> def f():
...     yield 1
...     return 2
...     yield 3  # 永远不会被执行
...
>>> g = f()
>>> next(g)
1
>>> next(g)  # return 引发 StopIteration
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration: 2
>>> next(g)  # 再次调用,StopIteration 变成无参了。
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

可以看到 return 引发了 StopIteration 异常,而 return 的值则成了该异常的第一个参数。

之前说过 yield from <sub generator> 表达式的值,就是该 <sub generator> 的 StopIteration 异常的第一个参数,因此:

>>> def f2():
...     a = yield from f()
...     yield a  # a 是 f() 中 return 的值
...
>>> list(f2())
[1, 2]

PEP 479 – Change StopIteration handling inside generators 修改了StopIteration 的行为,该 PEP 使人为 raise 的 StopIteration 引发一个 RuntimeError。该 PEP 在 Python 3.5 版本添加到 future 中,并在 Python 3.7 成为默认行为。因此除非你确实想要引发异常,否则应该使用 return 来结束一个 generator 并返回值。

先了解一下 进程线程协程与并发并行各种 IO 模型

asyncio 引入了两个新关键字:async 和 await,其中 async 能放在三个地方:

  1. async def:用于定义异步函数和异步生成器
    • 不含有 yield 的是 async def 定义的是协程函数(coroutine function),调用该函数返回协程对象(coroutine object),协程对象需要通过 EventLoop 运行。
    • 内部含有 yield 的 async def 定义的是异步生成器函数(asynchronous generator function),调用该函数返回异步生成器(async_generator)
      • 异步生成器只能用在 Coroutine 中
    • async def 中不允许使用 yield from
  2. async for:表示 for 迭代的是一个异步生成器,该 for 循环的每一次迭代,都是异步的。
    • 只能用在 async def 的内部
  3. async with:表示 with 管理的是一个异步上下文管理器(asynchronous context manager)
    • 该 context manager 的 enter 和 exit 两个步骤是异步的
    • 只能用在 async def 的内部

注意异步 generator、context manager,它的 protocol 都和同步的不同,不能混为一谈。具体而言,对同步 protocol xxx 函数,它的异步版本为 axxx,就是加个 a。

而 await,就相当于 yield from,差别在于 await 是异步的。还有我们关心的是 await 表达式的值,而 yield from 中我们更关心它向上层 yield 的值。

在 yield from 中,当前生成器调用另一个生成器,当前生成器会挂起,直到另一个生成器返回。

但是在 await 中,当前 Coroutine 挂起时, eventloop 会寻找其他 task 来跑,这就利用上了 IO 漫长的等待时间。

async for 是每次迭代都会 await 一次,如果迭代对象是 IO 操作,这个 IO 等待时间就会被利用上。

async with 也是同样,如果 context 的 enter 和 exit 是 IO 操作,这个 IO 时间就会被 eventloop 用于运行其他 task.

使用 asyncio 时,我们要用 async def 将所有的 IO 操作都定义成异步操作。然后在调用时,都使用 await/async for/async with 来调用。

首先,每个协程对象,都是一个独立的协程单元,协程对象之间可以异步运行。

协程需要放到 EventLoop 内运行,要运行一个协程 a,有三种方法:

  1. 通过 asyncio.run(coro) 运行一个协程。
    • 该方法会新建一个 EventLoop
  2. 在另一个协程 b 中通过 await 调用 a。当 b 运行时, a 也会被 task 运行。
  3. 通过 asyncio.create_task(coro),将需要运行的协程包装成 task,然后通过 task 相关的方法来异步运行它们。
    • asyncio.gather(*awaitable_objects): 并发执行所有的 task,阻塞到所有 task 结束。返回一个 result 列表。result 的列表顺序和 future 的顺序一致
    • asyncio.as_completed(aws, *, loop=None, timeout=None),和 gather 的区别在于,它返回一个异步迭代器,每次迭代都返回最先完成的一个 future.

concurrent.futures 是进程线程的异步执行,而 asyncio 是基于协程的单线程异步执行