從python協程理解tornado異步

博客原文地址:http://www.v2steve.com/2015/05/31/python/py_tornado_async/python

剛接觸tornado時候最疑惑的問題就是tornado.gen.coroutine是怎麼實現的。如何在代碼中用同步格式實現異步效果。看了幾回源碼發現其實就是python協程的一個具體應用。下面從生成器開始,說說tornado的異步。app

python協程

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的異步

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

相關文章
相關標籤/搜索