項目地址: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
這張圖(來自: 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 每日推送