標籤:
轉自: http://m.blog.csdn.net/blog/joeyon/41956027首先說下環境,python2.7,Tornado3.0
然後本文討論的不是Tornado底層如何用epoll處理socket通訊,而是如何在應用程式層非同步處理get/post請求。下面是本文:
同時在get或者post方法處理上應用@tornado.web.asynchronous和
@tornado.gen.engine裝飾器,可以非常方便的和有callback參數的非同步方法呼叫配合實現非阻塞請求處理。 這裡不想說官方的那個例子,因為官方的例子給的本來就是用非同步http用戶端來拉取資料然後自己轉到回呼函數執行,討論http用戶端的代碼不在本文範圍內。於是我用一個簡單的函數來示範,且這樣更能說明問題。 class
RegisterHandler(basehandler.BaseHandler):
@tornado.web.asynchronous
@tornado.gen.engine def
get(
self, *arg, **args): response = yield tornado.gen.Task(
self.method_while) print
"response",response
self.finish() def
method_while(
self, *arg, **args): callback(1)
這是我寫的一個請求處理的Handler,get請求上加了這兩個裝飾器,第一個裝飾器標明這個get函數是不會自動斷掉輸出資料流的,需要顯式的調用finish方法。這個裝飾器需要和@tornado.gen.engine一塊用。 重點到了,來看@tornado.gen.engine的源碼def
engine(func):
@functools.wraps(func) def
wrapper(*args, **kwargs): runner = None def
handle_exception(typ, value, tb): if runner is not None: return runner.handle_exception(typ, value, tb) return False with ExceptionStackContext(handle_exception) as deactivate: gen = func(*args, **kwargs) if isinstance(gen, types.GeneratorType): runner = Runner(gen, deactivate) runner.run() return assert gen is None, gen deactivate() return wrapper(我把原來有的注釋刪掉了) 我們直接看with語句的內部就可以了:gen = func(*args, **kwargs)if isinstance(gen, types.GeneratorType):
runner = Runner(gen, deactivate)runner.run()return
gen就是我們的get方法,gen=fun(*args,**kwargs)相當於gen=get(*args,**kwargs),get方法執行結果返回給gen?no~no!get方法裡有yield,意味著get(*args,**kwargs)返回的是一個產生器類型GeneratorType的資料,是不會馬上執行的,需要調用他的next或者send方法讓其執行到下一個yield方法處。關於產生器以及它的send和next的具體用法不在本文的討論範圍內,請查閱官方文檔。 可以看到它new了一個Runner,gen傳了進去,然後執行run方法,我們跟進Runner類的run方法的代碼:def
run(
self ): if
self.running or
self.finished:#判斷是否是在運行中或者已經結束,如果是立即返回 return try:
self.running = True #讓狀態為運行中 while True:#進入迴圈 if
self.exc_info is None:#上次迴圈沒有異常 try: if not
self.yield_point.is_ready():#判斷key是否可用 return next =
self.yield_point.get_result()#擷取yield運算式的結果給next except Exception:
self.exc_info = sys.exc_info() try: if
self.exc_info is not None:
self.had_exception = True exc_info =
self.exc_info
self.exc_info = None yielded =
self.gen.throw(*exc_info) else: yielded =
self.gen.send(next)#如果上次沒異常,get函數繼續執行,這裡把Task對象返回給yielded,相當於要迭代使用這個Task(最開頭標紅的程式碼片段),每次執行到yield都要把task重新賦給yielded except StopIteration:#如果沒有可以執行的了,返回
self.finished = True if
self.pending_callbacks and not
self.had_exception: raise LeakedCallbackError(
"finished without waiting for callbacks %r" %
self.pending_callbacks)
self.deactivate_stack_context()
self.deactivate_stack_context = None return except Exception:
self.finished = True raise if isinstance(yielded, list): yielded = Multi(yielded) if isinstance(yielded, YieldPoint):#如果yielded為Task類型(Task繼承於YieldPoint)
self.yield_point = yielded try:
self.yield_point.start(
self)#執行task的start方法 except Exception:
self.exc_info = sys.exc_info() else:
self.exc_info = (BadYieldError(
"yielded unknown object %r" % yielded),) finally:
self.running = False 具體步驟可以詳見上述代碼中的注釋,這裡我們就繼續看task的start方法:class
Task(YieldPoint):(省略其他代碼) def
start(
self, runner):
self.runner = runner
self.key = object() runner.register_callback(
self.key)
self.kwargs[
"callback"] = runner.result_callback(
self.key)
self.func(*
self.args, **
self.kwargs)(省略其他代碼) 這裡的func方法就是最上面的method_while,傳了一個callback參數進去:runner.result_callback(
self.key),然後執行該方法。為了討論這個callback參數的作用,我們假設這個method_while方法裡,沒有用到這個參數,即沒有執行callback:start方法在method_while執行完後就返回了,繼續執行Runner的run方法裡的那個迴圈的第二遍,它會直接在這一行if not
self.yield_point.is_ready():#判斷key是否可用 return直接返回,原因就是在results 裡這個key對應的值為None。 這裡涉及到了兩個名詞,results 和key,result是在哪裡的呢?是Runner的一個欄位,從下面的Runner的建構函式裡可以看到results是個字典 :class
Runner(object): def
__init__(
self, gen, deactivate_stack_context): (省略其他代碼)
self.pending_callbacks = set()
self.results = {} (省略其他代碼) 那麼key是怎麼設定到results這個字典裡的呢?是在Task的start方法裡,runner.register_callback(
self.key),具體代碼可以再看上面的Task類。這個register_callback是有點繞的,它將key設定到pending_callbacks裡面,這個pending_callbacks是一個set類型,即不可重複的集合,有這個集合的作用是為了每次都要從這裡面判斷key是否存在,key存在說明有個Task要執行。在上面的run方法中的判斷key是否可用的時候,is_ready每次都會先判斷key是否在這個pending_callbacks,沒有直接報異常,有的話才會去results去取結果。我們可以看到results一直是空啊,什麼時候設定的值呢??就是剛剛我們在method_while裡沒有執行的callback方法,即runner.result_callback(
self.key):def
result_callback(
self , key): def
inner(*args, **kwargs): if kwargs or len(args) > 1: result = Arguments(args, kwargs) elif args: result = args[ 0] else: result = None
self.set_result(key, result) return inner 可以看出來,他把callback實際調用時候的參數列表又寫進了results裡了,返回給yield運算式的值response。所以我們發現,一定要在自訂的方法中執行參數中的callback,把你想返回的資料寫進callback的參數裡,yield運算式的值就會是這個參數了,然後run方法繼續執行到下一個yield直到get方法完成。 比如最上面那個例子,列印結果為:response 1
最後來說這個非同步方案有什麼用呢?一說到非同步,新手肯定會以為是get/post方法阻塞了也沒關係,一個get請求過來,get處理函數如果阻塞了,是必然導致整個伺服器阻塞的!不要以為這個是相對web前端請求的並行非同步解決方案,單線程的Tornado畢竟是單線程,方法阻塞,必然導致伺服器阻塞,毋庸置疑的。 這個非同步是相對與get請求來說的,get方法在沒有加裝飾器@tornado.web.asynchronous的情況下,get方法結束後,請求會自動斷開,但是加上這個裝飾器後,get請求可以直接return且保持串連,直到顯式的調用finish方法才會關閉輸出資料流。這樣就萌生了非同步(非同步:把事情交給除了自己之外的人來做,自己不管)解決方案,get方法直接return,交給別的函數來做,做完了再回來。加上@tornado.gen.engine只是把上面這個思想實現了下,將代碼濃縮到最小而已,這並不表示非同步處理的函數或者這個get裡有死迴圈的話也能同時讓伺服器處理別的請求,你查詢資料庫花費10000s,那麼你的伺服器就肯定要死10000s的,這種問題的解決方案有四個:crouchDB(有http介面,非同步httpclient調用),最佳化當前資料庫,非同步httpclient調webservice,開線程。推薦第二個第三個,其次第四個,最後第一個(原因不解釋,貌似crouchDB口碑不好,我也不愛用,mongo黨飄過)。 就算是非同步httpclient,就像下面這樣
def handle_request(response): if response.error: print "Error:", response.error else: print response.bodyhttp_client = AsyncHTTPClient()http_client.fetch("http://www.google.com/", handle_request)也只是fetch過後不管socket如何拉資料而已,回調也是要在本線程執行的!在別處如果有耗時操作或者死迴圈的,它拉過來的資料是永遠進不到handle_request函數中的。
[轉]Tornado get/post請求非同步處理架構分析