PyTips 0x13 – Python 线程与协程(2)
admin
2023-07-31 01:45:31
0

项目地址:https://git.io/pytips

我之前翻译了Python 3.5 协程原理这篇文章之后尝试用了 Tornado + Motor 模式下的协程进行异步开发,确实感受到协程所带来的好处(至少是语法上的:D)。至于协程的 async/await 语法是如何由开始的 yield 生成器一步一步上位至 Python 的 async/await 组合语句,前面那篇翻译的文章里面讲得已经非常详尽了。我们知道协程的本质上是:

allowing multiple entry points for suspending and resuming execution at certain locations.

允许多个入口对程序进行挂起、继续执行等操作,我们首先想到的自然也是生成器:

def jump_range(upper):
    index = 0
    while index < upper:
        jump = yield index
        if jump is None:
            jump = 1
        index += jump
jump = jump_range(5)
print(jump)
print(jump.send(None))
print(jump.send(3))
print(jump.send(None))

0
3
4

后来又新增了 yield from 语法,可以将生成器串联起来:

def wait_index(i):
    # processing i...
    return (yield i)
def jump_range(upper):
    index = 0
    while index < upper:
        jump = yield from wait_index(index)
        if jump is None:
            jump = 1
        index += jump
jump = jump_range(5)
print(jump)
print(jump.send(None))
print(jump.send(3))
print(jump.send(None))

0
3
4

yield from/send 似乎已经满足了协程所定义的需求,最初也确实是用 @types.coroutine 修饰器将生成器转换成协程来使用,在 Python 3.5 之后则以专用的 async/await 取代了 @types.coroutine/yield from

class Wait(object):
    \"\"\"
    由于 Coroutine 协议规定 await 后只能跟 awaitable 对象,
    而 awaitable 对象必须是实现了 __await__ 方法且返回迭代器
    或者也是一个协程对象,
    因此这里临时实现一个 awaitable 对象。
    \"\"\"
    def __init__(self, index):
        self.index = index
    def __await__(self):
        return (yield self.index)
async def jump_range(upper):
    index = 0
    while index < upper:
        jump = await Wait(index)
        if jump is None:
            jump = 1
        index += jump
jump = jump_range(5)
print(jump)
print(jump.send(None))
print(jump.send(3))
print(jump.send(None))

0
3
4

与线程相比

协程的执行过程如下所示:

import asyncio
import time
import types

@types.coroutine
def _sum(x, y):
    print(\"Compute {} + {}...\".format(x, y))
    yield time.sleep(2.0)
    return x+y
@types.coroutine
def compute_sum(x, y):
    result = yield from _sum(x, y)
    print(\"{} + {} = {}\".format(x, y, result))
loop = asyncio.get_event_loop()
loop.run_until_complete(compute_sum(0,0))
Compute 0 + 0...
0 + 0 = 0

这张图(来自: PyDocs: 18.5.3. Tasks and coroutines)清楚地描绘了由事件循环调度的协程的执行过程,上面的例子中事件循环的队列里只有一个协程,如果要与上一部分中线程实现的并发的例子相比较,只要向事件循环的任务队列中添加协程即可:

import asyncio
import time

# 上面的例子为了从生成器过度,下面全部改用 async/await 语法
async def _sum(x, y):
    print(\"Compute {} + {}...\".format(x, y))
    await asyncio.sleep(2.0)
    return x+y
async def compute_sum(x, y):
    result = await _sum(x, y)
    print(\"{} + {} = {}\".format(x, y, result))

start = time.time()
loop = asyncio.get_event_loop()
tasks = [
    asyncio.ensure_future(compute_sum(0, 0)),
    asyncio.ensure_future(compute_sum(1, 1)),
    asyncio.ensure_future(compute_sum(2, 2)),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print(\"Total elapsed time {}\".format(time.time() - start))
Compute 0 + 0...
Compute 1 + 1...
Compute 2 + 2...
0 + 0 = 0
1 + 1 = 2
2 + 2 = 4
Total elapsed time 2.0042951107025146

总结

这两篇主要关于 Python 中的线程与协程的一些基本原理与用法,为此我搜索了不少参考文章与链接,对我自己理解它们的原理与应用场景也有很大的帮助(当然也有可能存在理解不到位的地方,欢迎指正)。当然在这里还是主要关注基于 Python 的语法与应用,如果想要了解更多底层实现的细节,可能需要从系统调度等底层技术细节开始学习(几年前我记得翻阅过《深入理解LINUX内核》这本书,虽然大部分细节已经记不清楚了,但对于理解其它人的分析、总结还是有一定帮助的)。这里讨论的基于协程的异步主要是借助于事件循环(由asyncio标准库提供),包括上文中的示意图,看起来很容易让人联想到 Node.js 的事件循环 & 回调,但是协程与回调也还是有区别的,具体就不在这里展开了,可以参考下面第一条参考链接。


欢迎关注公众号 PyHub 每日推送

参考

  1. Python 中的进程、线程、协程、同步、异步、回调

  2. 我是一个线程

  3. Concurrency is not Parallelism

  4. A Curious Course on Coroutines and Concurrency

  5. PyDocs: 17.1. threading — Thread-based parallelism

  6. PyDocs: 18.5.3. Tasks and coroutines

  7. [译] Python 3.5 协程究竟是个啥

  8. 协程的好处是什么? – crazybie 的回答

  9. Py3-cookbook:第十二章:并发编程

  10. Quora: What are the differences between parallel, concurrent and asynchronous programming?

  11. Real-time apps with gevent-socketio

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...