1. 程式人生 > >Tornado使用者指引(一)-----------非同步和非阻塞I/O

Tornado使用者指引(一)-----------非同步和非阻塞I/O

    • 摘要:非同步和非阻塞I/O實時WEB的特性是經常需要為每個使用者端維持一個長時間存活但是大部分時候空閒的連線。在傳統的同步式web伺服器中,這主要通過為每個使用者建立一個執行緒來實現,這樣的代價是十分昂貴的。為了最大限度地減少併發成本,Tornado使用單執行緒的事件迴圈機制(linux中是基於epoll的).這就意味著所有的應用程式碼都應該是非同步或非阻塞的,因為同時只能有一個操作是活動的。儘管非同步和非阻塞這2個術語是密切相關的,並且通常可以互換使用,但並不完全相同。阻塞當一個函式在等待某些事
    • 非同步和非阻塞I/O

      實時WEB的特性是經常需要為每個使用者端維持一個長時間存活但是大部分時候空閒的連線。在傳統的同步式web伺服器中,這主要通過為每個使用者建立一個執行緒來實現,這樣的代價是十分昂貴的。


      為了最大限度地減少併發成本,Tornado使用單執行緒的事件迴圈機制(linux中是基於epoll的).這就意味著所有的應用程式碼都應該是非同步或非阻塞的,因為同時只能有一個操作是活動的。 

      儘管非同步和非阻塞這2個術語是密切相關的,並且通常可以互換使用,但並不完全相同。 

      阻塞
      當一個函式在等待某些事件的時候就會阻塞。一個函式阻塞的原因有很多:網路I/O,磁碟I/O,鎖等等。
      實際上,所有的函式在使用CPU的時候,都多多少少的會阻塞,至少會有一點點。
      下面有一個極端的例子演示了為什麼CPU阻塞比其他型別的阻塞還要嚴重。
      密碼雜湊函式比如bcrypt,被設計為要使用數百ms的CPU時間,遠遠大於通常的網路或磁碟訪問。

      一個函式可以在某些方面阻塞,也可以在某些方面非阻塞。
      比如,在預設配置下,tornado.httpclient會在DNS解析時阻塞但是不會在其他網路訪問時阻塞(為了緩解這種狀況,可以使用ThreadResolver和tornado.curl_httpclient
      它們是通過合適的配置用libcurl來構建的)
      在tornado的背景中,我們一般只討論網路I/O下的阻塞,儘管其它型別的阻塞也被最小化了。

      非同步

      一個非同步函式在它完成之前返回,在應用程式觸發一些未來的動作之前,需要在後臺做一些工作。這與同步函式正好相反,同步函式在返回之前把所有要做的事件都做完。
      有許多型別的非同步介面:
      1.回撥函式引數
      2.返回一個佔位符(Future,Promise,Deferred)
      3.傳送到佇列
      4.回撥函式註冊(比如POSIX標準的訊號)
      不管何種型別的非同步介面,呼叫者都採用不同方式使用這些非同步介面。把同步函式轉換為非同步函式需要採用一定的方法,這種轉換對呼叫者是透明的。(一些系統比如gevent使用
      輕量級的執行緒來提供與非同步系統相似的效能,但是它們並不是真正地非同步)

      舉例
      下面是一個同步函式的例子:
      from tornado.httpclient import HTTPClient

      def synchronous_fetch(url):
          http_client = HTTPClient()
          response = http_client.fetch(url)
          return response.body

      然後是一個通過回撥函式將同樣功能的函式變為非同步的例子:
      from tornado.httpclient import AsyncHTTPClient

      def asynchronous_fetch(url, callback):
          http_client = AsyncHTTPClient()
          def handle_response(response):
              callback(response.body)
          http_client.fetch(url, callback=handle_response)

      還可以用Future代替回撥函式:
      from tornado.concurrent import Future

      def async_fetch_future(url):
          http_client = AsyncHTTPClient()
          my_future = Future()
          fetch_future = http_client.fetch(url)
          fetch_future.add_done_callback(
              lambda f: my_future.set_result(f.result()))
          return my_future

      原始的Future是比較複雜的,但是Tornado無條件地建議在實際程式設計中使用Future,因為有以下2點好處:
      1.使錯誤處理更加一致,因為Future.result方法可以簡單地丟擲一個異常
      2.Future很好地使用了coroutines.Coroutines將在下一節進行更深的討論。
      下面是一個coroutine版本的非同步函式,與最開始的同步函式十分相似:
      from tornado import gen

      @gen.coroutine
      def fetch_coroutine(url):
          http_client = AsyncHTTPClient()
          response = yield http_client.fetch(url)
          raise gen.Return(response.body)

      raise gen.Return(response.body)語句是一個包裝在python2和python3.2裡,因為python的這些版本不支援將生成器函式作為返回值。
      為了克服這個,Tornado coroutines丟擲一個特殊種類的異常,稱為Return. 
      coroutine捕獲這個異常並將它當作一個返回值。
      在Python3.3和之後的版本里面,使用"return response.body"的效果是一樣的。