tornado 源碼之 coroutine 分析
tornado 的協程原理分析
版本:4.3.0
爲支持異步,tornado 實現了一個協程庫。
tornado 實現的協程框架有下面幾個特點:
- 支持 python 2.7,沒有使用 yield from
特性,純粹使用 yield 實現 - 使用拋出異常的方式從協程返回值
- 採用 Future 類代理協程(保存協程的執行結果,當攜程執行結束時,調用註冊的回調函數)
- 使用 IOLoop 事件循環,當事件發生時在循環中調用註冊的回調,驅動協程向前執行
由此可見,這是 python 協程的一個經典的實現。
本文將實現一個類似 tornado 實現的基礎協程框架,並闡述相應的原理。
外部庫
使用 time 來實現定時器回調的時間計算。
bisect 的 insort 方法維護一個時間有限的定時器隊列。
functools 的 partial 方法綁定函數部分參數。
使用 backports_abc 導入 Generator 來判斷函數是否是生成器。
import time
import bisect
import functools
from backports_abc import Generator as GeneratorType
Future
是一個穿梭於協程和調度器之間的信使。
提供了回調函數註冊(當異步事件完成後,調用註冊的回調)、中間結果保存、結束結果返回等功能
add_done_callback 註冊回調函數,當 Future 被解決時,改回調函數被調用。
set_result 設置最終的狀態,並且調用已註冊的回調函數
協程中的每一個 yield 對應一個協程,相應的對應一個 Future 對象,譬如:
@coroutine
def routine_main():
yield routine_simple()
yield sleep(1)
這裏的 routine_simple() 和 sleep(1) 分別對應一個協程,同時有一個 Future 對應。
class Future(object):
def __init__(self):
self._done = False
self._callbacks = []
self._result = None
def _set_done(self):
self._done = True
for cb in self._callbacks:
cb(self)
self._callbacks = None
def done(self):
return self._done
def add_done_callback(self, fn):
if self._done:
fn(self)
else:
self._callbacks.append(fn)
def set_result(self, result):
self._result = result
self._set_done()
def result(self):
return self._result
IOLoop
這裏的 IOLoop 去掉了 tornado 源代碼中 IO 相關部分,只保留了基本需要的功能,如果命名爲 CoroutineLoop 更貼切。
這裏的 IOLoop 提供基本的回調功能。它是一個線程循環,在循環中完成兩件事:
- 檢測有沒有註冊的回調並執行
- 檢測有沒有到期的定時器回調並執行
程序中註冊的回調事件,最終都會在此處執行。
可以認爲,協程程序本身、協程的驅動程序 都會在此處執行。
協程本身使用 wrapper 包裝,並最後註冊到 IOLoop 的事件回調,所以它的從預激到結束的代碼全部在 IOLoop 回調中執行。
而協程預激後,會把 Runner.run() 函數註冊到 IOLoop 的事件回調,以驅動協程向前運行。
理解這一點對於理解協程的運行原理至關重要。
這就是單線程異步的基本原理。因爲都在一個線程循環中執行,我們可以不用處理多線程需要面對的各種繁瑣的事情。
coroutine
協程返回值
sleep
Runner
因爲沒有使用 yield from,協程無法直接返回值,所以使用拋出異常的方式返回。
copyright
author:bigfish
copyright: 許可協議 知識共享署名-非商業性使用 4.0 國際許可協議