常見問題¶
為什麼這個使用 time.sleep()
的範例沒有平行執行?¶
許多人第一次接觸 Tornado 的並行程式碼看起來像這樣
class BadExampleHandler(RequestHandler):
def get(self):
for i in range(5):
print(i)
time.sleep(1)
同時獲取這個處理器兩次,你會看到第二個五秒倒數計時在第一個完全完成後才開始。原因在於 time.sleep
是一個阻塞函式:它不允許控制權返回給 IOLoop
,以便可以執行其他處理器。
當然,time.sleep
在這些範例中只是一個佔位符,重點是顯示當處理器中的某些東西變慢時會發生什麼。無論真正的程式碼在做什麼,要實現並行,都必須用非阻塞的等效程式碼替換阻塞程式碼。這意味著以下三種情況之一
尋找協程友善的等效程式碼。 對於
time.sleep
,請改用tornado.gen.sleep
(或asyncio.sleep
)class CoroutineSleepHandler(RequestHandler): async def get(self): for i in range(5): print(i) await gen.sleep(1)
當這個選項可用時,它通常是最好的方法。請參閱 Tornado wiki 以獲取可能有用的非同步程式庫的連結。
尋找基於回呼的等效程式碼。 與第一個選項類似,許多任務都有基於回呼的程式庫,雖然它們比專為協程設計的程式庫使用起來稍微複雜一些。將基於回呼的函式改編成 future
class CoroutineTimeoutHandler(RequestHandler): async def get(self): io_loop = IOLoop.current() for i in range(5): print(i) f = tornado.concurrent.Future() do_something_with_callback(f.set_result) result = await f
同樣,Tornado wiki 可以幫助您找到合適的程式庫。
在另一個執行緒上執行阻塞程式碼。 當沒有可用的非同步程式庫時,可以使用
concurrent.futures.ThreadPoolExecutor
在另一個執行緒上執行任何阻塞程式碼。這是一個通用的解決方案,可用於任何阻塞函式,無論是否存在非同步的對應程式碼class ThreadPoolHandler(RequestHandler): async def get(self): for i in range(5): print(i) await IOLoop.current().run_in_executor(None, time.sleep, 1)
有關阻塞和非同步函式的詳細資訊,請參閱 Tornado 使用者指南的 非同步 I/O 章節。
我的程式碼是非同步的。為什麼在兩個瀏覽器分頁中沒有平行執行?¶
即使處理器是非同步且非阻塞的,要驗證這一點也可能會出奇地棘手。瀏覽器會識別出您正嘗試在兩個不同的分頁中載入相同的頁面,並將第二個請求延遲到第一個請求完成後才發出。要解決這個問題並查看伺服器實際上是否平行運作,請執行以下兩種操作之一
在您的 URL 中加入一些內容,使其獨一無二。不要在兩個分頁中都載入
https://127.0.0.1:8888
,而是在一個分頁中載入https://127.0.0.1:8888/?x=1
,在另一個分頁中載入https://127.0.0.1:8888/?x=2
。使用兩個不同的瀏覽器。例如,即使在 Chrome 分頁中載入相同的 URL,Firefox 也能夠載入該 URL。