PyTips 0x13 - Python 線程與協程(2)

項目地址:https://git.io/pytipshtml

我以前翻譯了Python 3.5 協程原理這篇文章以後嘗試用了 Tornado + Motor 模式下的協程進行異步開發,確實感覺到協程所帶來的好處(至少是語法上的:D)。至於協程的 async/await 語法是如何由開始的 yield 生成器一步一步上位至 Python 的 async/await 組合語句,前面那篇翻譯的文章裏面講得已經很是詳盡了。咱們知道協程的本質上是:python

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

容許多個入口對程序進行掛起、繼續執行等操做,咱們首先想到的天然也是生成器:github

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))
<generator object jump_range at 0x10e283518>
0
3
4

後來又新增了 yield from 語法,能夠將生成器串聯起來:golang

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))
<generator object jump_range at 0x10e22a780>
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))
<coroutine object jump_range at 0x10e2837d8>
0
3
4

與線程相比segmentfault

協程的執行過程以下所示:併發

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

tulip_coro

這張圖(來自: PyDocs: 18.5.3. Tasks and coroutines)清楚地描繪了由事件循環調度的協程的執行過程,上面的例子中事件循環的隊列裏只有一個協程,若是要與上一部分中線程實現的併發的例子相比較,只要向事件循環的任務隊列中添加協程便可:app

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 每日推送

歡迎關注公衆號 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

相關文章
相關標籤/搜索