博客原文地址:http://www.v2steve.com/2015/05/31/python/py_tornado_async/python
剛接觸tornado時候最疑惑的問題就是tornado.gen.coroutine是怎麼實現的。如何在代碼中用同步格式實現異步效果。看了幾回源碼發現其實就是python協程的一個具體應用。下面從生成器開始,說說tornado的異步。app
python利用yield關鍵字實現生成器,yield就像生化危機裏的T病毒,被yield感染的函數都不單單是函數,而是一個函數生成器。函數生成器實例化後能夠不斷調用next方法吐出yield後的值。見下面代碼:異步
def gen(): while True: a = yield print a b = gen() b.next() # 直接返回,無輸出 b.send(16) # 打印16
如上面代碼,函數gen()由於存在yield
關鍵字,就變成了一個生成器函數,實例化這個生成器函數gen獲得b,調用b的next()
方法,會執行gen(),直到遇到第一個yield關鍵字後返回yield後的值(第一次執行直接返回,沒有返回值),這時若是繼續調用b.next(),就會每次讀到yield處返回一個值。可是假若調用b的send()方法,就會傳遞一個值到給生成器,CPU會從剛纔掛起的狀態開始繼續,從yield後傳入此值繼續執行直到再遇到yield。async
這其實就是用生成器來實現一個協程的例子,程序在須要跳轉的地方被掛起,CPU跳轉到其餘代碼執行,一旦須要繼續剛纔的狀態,就用send發送一個值。可是這有什麼用呢?沒錯,就是接下來要講的異步IO。當執行到一段須要等待IO返回結果的代碼時,爲了提升效率,能夠將當前執行狀態掛起,轉而去幹其餘事情。一旦IO處理完畢,就觸發回調函數,回調函數裏執行上述的send()方法,將處理結果傳遞迴以前的狀態裏,程序再次回到以前掛起的狀態,繼續執行剛纔未完成的操做。函數
tornado使用本身的異步裝飾器gen.coroutine裝飾須要異步操做的handler的get(或post)方法:tornado
@tornado.gen.coroutine def get(): result = yield foo() return result
下面看看這個裝飾器作了哪些操做(不瞭解裝飾器的朋友請自行搜索一下,這是python的最好用的語法糖)。我根據須要精簡了部分代碼,請自行查看源碼瞭解更多:oop
def _make_coroutine_wrapper(func, replace_callback): @functools.wraps(func) def wrapper(*args, **kwargs): future = TracebackFuture() if replace_callback and 'callback' in kwargs: callback = kwargs.pop('callback') IOLoop.current().add_future( future, lambda future: callback(future.result())) try: result = func(*args, **kwargs) except (Return, StopIteration) as e: result = getattr(e, 'value', None) except Exception: future.set_exc_info(sys.exc_info()) return future else: if isinstance(result, types.GeneratorType): try: orig_stack_contexts = stack_context._state.contexts yielded = next(result) if stack_context._state.contexts is not orig_stack_contexts: yielded = TracebackFuture() yielded.set_exception( stack_context.StackContextInconsistentError( 'stack_context inconsistency (probably caused ' 'by yield within a "with StackContext" block)')) except (StopIteration, Return) as e: future.set_result(getattr(e, 'value', None)) except Exception: future.set_exc_info(sys.exc_info()) else: Runner(result, future, yielded) try: return future finally: future.set_result(result) return future return wrapper
分析一下,首先開頭部分判斷若是有callback函數,就把callback加入ioloop。不然把被修飾的func實例化爲一個生成器result(即上面代碼裏的get()函數,由於yield的緣故,get已經成爲一個生成器函數),而後執行一次next(result),注意,在上上面的代碼中,get()中yield的方法foo()是異步操做,因此通知IO後沒有等待,直接return,就像最開始說的協程示例同樣,修飾器裏的next(result)也直接返回,異步操做被掛起,以後實例化一個Runner,Runner內部會將本身的callback放入ioloop,callback中包含了send方法。一旦IO處理完畢,ioloop就調用callback,callback再調用send將結果塞回yield以後的代碼處,CPU跳回來繼續執行以前掛起的函數。 整個過程下來,代碼看起來是從頭至尾行雲流水,可是內部實現的邏輯倒是利用python的協程,讓CPU在不一樣的代碼間自如跳轉,爲IO處理實現異步化。post