常見問題

為什麼這個使用 time.sleep() 的範例沒有平行執行?

許多人第一次接觸 Tornado 的並行程式碼看起來像這樣

class BadExampleHandler(RequestHandler):
    def get(self):
        for i in range(5):
            print(i)
            time.sleep(1)

同時獲取這個處理器兩次,你會看到第二個五秒倒數計時在第一個完全完成後才開始。原因在於 time.sleep 是一個阻塞函式:它不允許控制權返回給 IOLoop,以便可以執行其他處理器。

當然,time.sleep 在這些範例中只是一個佔位符,重點是顯示當處理器中的某些東西變慢時會發生什麼。無論真正的程式碼在做什麼,要實現並行,都必須用非阻塞的等效程式碼替換阻塞程式碼。這意味著以下三種情況之一

  1. 尋找協程友善的等效程式碼。 對於 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 以獲取可能有用的非同步程式庫的連結。

  2. 尋找基於回呼的等效程式碼。 與第一個選項類似,許多任務都有基於回呼的程式庫,雖然它們比專為協程設計的程式庫使用起來稍微複雜一些。將基於回呼的函式改編成 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 可以幫助您找到合適的程式庫。

  3. 在另一個執行緒上執行阻塞程式碼。 當沒有可用的非同步程式庫時,可以使用 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。