资讯专栏INFORMATION COLUMN

PyTips 0x13 - Python 线程与协程(2)

史占广 / 2200人阅读

摘要:项目地址我之前翻译了协程原理这篇文章之后尝试用了模式下的协程进行异步开发,确实感受到协程所带来的好处至少是语法上的。

项目地址: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 每日推送

参考

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

我是一个线程

Concurrency is not Parallelism

A Curious Course on Coroutines and Concurrency

PyDocs: 17.1. threading — Thread-based parallelism

PyDocs: 18.5.3. Tasks and coroutines

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

协程的好处是什么? - crazybie 的回答

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

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

Real-time apps with gevent-socketio

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37866.html

相关文章

  • PyTips 0x 12 - Python 线程协程(1)

    摘要:中关于线程的标准库是,之前在版本中的在之后更名为,无论是还是都应该尽量避免使用较为底层的而应该使用。而与线程相比,协程尤其是结合事件循环无论在编程模型还是语法上,看起来都是非常友好的单线程同步过程。 项目地址:https://git.io/pytips 要说到线程(Thread)与协程(Coroutine)似乎总是需要从并行(Parallelism)与并发(Concurrency)谈起...

    el09xccxy 评论0 收藏0
  • Python协程(真才实学,想学的进来)

    摘要:所以与多线程相比,线程的数量越多,协程性能的优势越明显。值得一提的是,在此过程中,只有一个线程在执行,因此这与多线程的概念是不一样的。 真正有知识的人的成长过程,就像麦穗的成长过程:麦穗空的时候,麦子长得很快,麦穗骄傲地高高昂起,但是,麦穗成熟饱满时,它们开始谦虚,垂下麦芒。 ——蒙田《蒙田随笔全集》 上篇论述了关于python多线程是否是鸡肋的问题,得到了一些网友的认可,当然也有...

    lykops 评论0 收藏0
  • Python:Tornado 第二章:实战演练:开发Tornado网站:第六节:异步协程

    摘要:上一篇文章第二章实战演练开发网站第五节输出相应函数下一篇文章第二章实战演练开发网站第七节安全机制有两种方式可改变同步的处理流程异步化针对的处理函数使用修饰器,将默认的同步机制改为异步机制。使用异步对象处理耗时操作,比如本例的。 上一篇文章:Python:Tornado 第二章:实战演练:开发Tornado网站:第五节:RequestHandler:输出相应函数下一篇文章:Python:...

    cod7ce 评论0 收藏0
  • 谈谈Python协程技术的演进

    摘要:事件循环是异步编程的底层基石。对事件集合进行轮询,调用回调函数等一轮事件循环结束,循环往复。协程直接利用代码的执行位置来表示状态,而回调则是维护了一堆数据结构来处理状态。时代的协程技术主要是,另一个比较小众。 Coding Crush Python开发工程师 主要负责岂安科技业务风险情报系统redq。 引言 1.1. 存储器山 存储器山是 Randal Bryant 在《深入...

    zhiwei 评论0 收藏0
  • Python中的并发处理之使用asyncio包

    摘要:并发用于制定方案,用来解决可能但未必并行的问题。在协程中使用需要注意两点使用链接的多个协程最终必须由不是协程的调用方驱动,调用方显式或隐式在最外层委派生成器上调用函数或方法。对象可以取消取消后会在协程当前暂停的处抛出异常。 导语:本文章记录了本人在学习Python基础之控制流程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、了解asyncio...

    tuniutech 评论0 收藏0

发表评论

0条评论

史占广

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<