99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Tornado學(xué)習(xí)之協(xié)程詳解

發(fā)布時(shí)間:2017-02-23 15:42  回復(fù):0  查看:3168   最后回復(fù):2017-02-23 15:42  
本文和大家分享的主要是 python開發(fā)中Tornado 協(xié)程相關(guān)問題,一起來看看吧,希望對(duì)大家學(xué)習(xí)和使用這部分內(nèi)容有所幫助。
   什么是協(xié)程
  我們經(jīng)常使用的函數(shù)又稱子例程(subroutine) ,往往只有一個(gè)入口(函數(shù)的第一行),一個(gè)出口( return 、拋出異常)。子例程在入口時(shí)獲得控制權(quán),在出口時(shí)把控制權(quán)交還給調(diào)用者。一旦交還控制權(quán),就意味著例程的結(jié)束,函數(shù)中做的所有工作以及保存在局部變量中的數(shù)據(jù)都將被釋放。而協(xié)程可以有多個(gè)入口點(diǎn),允許從一個(gè)入口點(diǎn)執(zhí)行到下一個(gè)入口點(diǎn)之前暫停,保存執(zhí)行狀態(tài);等到合適的時(shí)機(jī)恢復(fù)執(zhí)行狀態(tài),從下一個(gè)入口點(diǎn)重新開始執(zhí)行。
  "Subroutines are special cases of ... coroutines." –Donald Knuth.
  可以把協(xié)程看做是子例程的泛化形式。
  在Python 中,協(xié)程基于生成器。它可以有多個(gè)出口和入口。出口有兩種類型:一種是 return ,用于永久交還控制權(quán),效果同子例程;另一種是 yield ,用于暫時(shí)交還控制權(quán),函數(shù)將來還會(huì)收回控制權(quán)。當(dāng)然入口也有兩種:一種是函數(shù)的第一行;另一種是上次 yield 的那一行。
  Tornado 中的協(xié)程
  Tornado 典型的協(xié)程例子:
  class GenAsyncHandler(RequestHandler): @gen.coroutine
  def get(self):
  http_client = AsyncHTTPClient()
  response = yield http_client.fetch("http://example.com")
  do_something_with_response(response)
  self.render("template.html")
  我們?yōu)檫@個(gè)Handler get() 添加了裝飾器 gen.coroutine ,從而使其成為一個(gè)協(xié)程。不難理解,面對(duì)耗時(shí)的操作,利用協(xié)程可以達(dá)到異步的效果,需要等待的時(shí)候 yield 出去,等待結(jié)束后重新回來繼續(xù)執(zhí)行。如例子中 get 需要等待http_client.fetch("http://example.com")  的結(jié)果,因此通過yield 返回;當(dāng)獲取到結(jié)果后, get 函數(shù)從上次離開處繼續(xù)運(yùn)行,且 response 被賦值為 http_client.fetch("http://example.com")  的結(jié)果。
  這主要涉及到以下幾樣?xùn)|西:
  1.Future
  Future 的設(shè)計(jì)目標(biāo)是作為協(xié)程 (coroutine) IOLoop 的媒介,從而將協(xié)程和 IOLoop 關(guān)聯(lián)起來。
  Future concurrent.py 中定義,是異步操作結(jié)果的占位符,用于等待結(jié)果返回。通常作為函數(shù) IOLoop.add_future() 的參數(shù)或 gen.coroutine 協(xié)程中 yield 的返回值。
  等到結(jié)果返回時(shí),外部可以通過調(diào)用set_result() 設(shè)置真正的結(jié)果,然后調(diào)用所有回調(diào)函數(shù),恢復(fù)協(xié)程的執(zhí)行。
  2.IOLoop
  IOLoop 是一個(gè) I/O 事件循環(huán),用于調(diào)度 socket 相關(guān)的連接、響應(yīng)、異步讀寫等網(wǎng)絡(luò)事件,并支持在事件循環(huán)中添加回調(diào) (callback) 和定時(shí)回調(diào) (timeout) 。在支持的平臺(tái)上,默認(rèn)使用 epoll 進(jìn)行 I/O 多路復(fù)用處理。
  IOLoop Tornado 的核心,絕大部分模塊都依賴于 IOLoop 的調(diào)度。在協(xié)程運(yùn)行環(huán)境中, IOLoop 擔(dān)任著協(xié)程調(diào)度器的角色,能夠讓暫停的協(xié)程重新獲得控制權(quán),從而能繼續(xù)執(zhí)行。
  IOLoop 通過 add_future() 對(duì) Future 的支持:
  def add_future(self, future, callback):
  assert is_future(future)
  callback = stack_context.wrap(callback)
  future.add_done_callback(lambda future: self.add_callback(callback, future))
  通過調(diào)用future add_done_callback() ,使當(dāng) future 在操作完成時(shí),能夠通過 add_callback callback 添加到 IOLoop 中,讓 callback IOLoop 下一次迭代中執(zhí)行 ( 不在本輪是為了避免餓死 ) 。
3.Runner
  Runner coroutine 都在 gen.py 中定義。 Runner coroutine 裝飾器創(chuàng)建,用于維護(hù)掛起的回調(diào)函數(shù)及結(jié)果的相關(guān)信息,包括中間結(jié)果 (future) 和最終結(jié)果 (result_future) 。
  4.coroutine
  gen.coroutine 是一個(gè)裝飾器,負(fù)責(zé)將普通函數(shù)包裝成協(xié)程。功能包括:
  · 調(diào)用函數(shù),如果該函數(shù)有 yield ,則返回生成器。否則立即得到結(jié)果,不屬于協(xié)程。
  · 通過 next() 執(zhí)行生成器,如果未能結(jié)束 ( 遇到 yield) ,則生成中間類 Runner 對(duì)象,用于保存生成器、 yield 返回值和最終返回值。
  · 創(chuàng)建 Runner 。
  結(jié)合以上幾樣?xùn)|西,協(xié)程的具體流程整理如下:
  每一次協(xié)程調(diào)用yield 釋放控制權(quán)后:
 ?。?/span>> [Runner]handle_yield
  處理yield 返回的結(jié)果
  -> [Runner]ioloop.add_future(self.future, lambda f: self.run())
  將結(jié)果構(gòu)造成future 后添加到ioloop
  -> [future]add_done_callback(lambda future: self.add_callback(callback, future))
  將Runner.run() 加入到完成時(shí)的回調(diào)函數(shù)列表中
  ??? 觸發(fā):
  -> [future]set_result
  已經(jīng)得到future 的結(jié)果,設(shè)置之
  -> [future]_set_done
  調(diào)用future 所有回調(diào)函數(shù) (_callbacks)
  -> [ioloop]add_callback(callback, future)
  callback [Runner]add_future 添加的那個(gè),即 [Runner]self.run() ,將在下一輪循環(huán)被執(zhí)行
  -> [Runner]self.run()
  取出Runner self.future( 上次yield 的返回值)
  1.  如果 future 未完成,return ,流程結(jié)束,等待下一次set_result
  2.  如果 future 完成
  -> [Runner]yielded = self.gen.send(value)
  通過send future result 發(fā)送給協(xié)程,并讓其恢復(fù)執(zhí)行:
  1.  如果協(xié)程結(jié)束 ( yield )
  -> [Runner]self.result_future.set_result
  設(shè)置最終的結(jié)果result_future
  2.  未結(jié)束 ( 再次遇到yield)
  -> [Runner]handle_yield
  則再次調(diào)用handle_yield
  從以上的流程可以看出,每一次協(xié)程調(diào)用yield 釋放控制權(quán)后的恢復(fù),都依賴于 set_result() 的調(diào)用。當(dāng)我們把目光看向總調(diào)度 IOLoop 時(shí),可以發(fā)現(xiàn) IOLoop 只是忠實(shí)地在每一輪迭代中調(diào)用那些就緒的回調(diào)函數(shù),并沒有主動(dòng)調(diào)用 set_result() 的能力。
  那么,在Tornado 中,這個(gè) set_result() 到底是誰調(diào)用?在何時(shí)調(diào)用?
  搜遍了Tornado 的代碼,主要找到以下幾個(gè)調(diào)用點(diǎn):
  1. coroutine 裝飾器中,如果裝飾的函數(shù)調(diào)用后直接結(jié)束 ( yield) ,直接 set_result() 。
  2. [Runner] run() 中,如果調(diào)用 send 后協(xié)程結(jié)束,對(duì) result_future 進(jìn)行 set_result() 。
3. 取決于 yield 后阻塞操作的具體實(shí)現(xiàn),下面以例子中 AsyncHTTPClient fetch() 來進(jìn)行分析。
  AsyncHTTPClient fetch() 是一個(gè)異步操作,其構(gòu)造了一個(gè) HTTP 請(qǐng)求,然后調(diào)用 fetch_impl() ,返回一個(gè) future fetch_impl() 取決于 AsyncHTTPClient 的具體實(shí)現(xiàn),默認(rèn)情況下, AsyncHTTPClient 生成的是子類 SimpleAsyncHTTPClient 的實(shí)例,所以主要看 SimpleAsyncHTTPClient fetch_impl()
  def fetch_impl(self, request, callback):
  key = object()
  self.queue.append((key, request, callback))
  if not len(self.active) < self.max_clients:
  timeout_handle = self.io_loop.add_timeout(
  self.io_loop.time() + min(request.connect_timeout,
  request.request_timeout),
  functools.partial(self._on_timeout, key))
  else:
  timeout_handle = None
  self.waiting[key] = (request, callback, timeout_handle)
  self._process_queue()
  if self.queue:
  gen_log.debug("max_clients limit reached, request queued. "
  "%d active, %d queued requests." % (len(self.active), len(self.queue)))
  fetch_impl() 接受兩個(gè)參數(shù), request fetch() 中構(gòu)造的 HTTP 請(qǐng)求, callback fetch 中的回調(diào)函數(shù) handle_response
  def handle_response(response):
  if raise_error and response.error:
  future.set_exception(response.error)
  else:
  future.set_result(response)
  在handle_response() 中,調(diào)用了我們期待的 set_result() 。所以我們把目光轉(zhuǎn)移到 fetch_impl() callback 。在 fetch_impl() 中,函數(shù)先將 callback 加到隊(duì)列中,然后通過 _process_queue() 處理掉,處理時(shí)調(diào)用 _handle_request()
  def _handle_request(self, request, release_callback, final_callback):
  self._connection_class()(
  self.io_loop, self, request, release_callback,
  final_callback, self.max_buffer_size, self.tcp_client,
  self.max_header_size, self.max_body_size)
  這里構(gòu)造了一個(gè)_connection_class 對(duì)象,即 HTTPConnection 。 HTTPConnection 通過 self.tcp_client.connect() 來建立 TCP 連接,然后通過該連接發(fā)送 HTTP 請(qǐng)求, 在超時(shí) (timeout) 或完成 (finish) 時(shí)調(diào)用 callback tcp_client 在建立異步 TCP 連接時(shí),先進(jìn)行 DNS 解析 ( 又是協(xié)程 ) ,然后建立 socket 來構(gòu)造 IOStream 對(duì)象,最后調(diào)用 IOStream.connect() 。在 IOStream.connect() 的過程中,我們看到了關(guān)鍵操作:
  self.io_loop.add_handler(self.fileno(), self._handle_events, self._state)
  還記得我們前面說過的IOLoop 嗎? IOLoop 可以添加 socket 、 callback timeout ,并當(dāng)它們就緒時(shí)調(diào)用相應(yīng)的回調(diào)函數(shù)。這里 add_handler 處理的就是 socket 的多路復(fù)用,默認(rèn)的實(shí)現(xiàn)是 epoll 。當(dāng) epoll 中該 socket 就緒時(shí),相關(guān)函數(shù)得以回調(diào)。于是 tcp_client 讀取 socket 內(nèi)容獲得 HTTP response handle_response() 被調(diào)用,最終 set_result() 被調(diào)用。
  到這里我們恍然大悟,AsyncHTTPClient set_result() 調(diào)用依賴于 IO 多路復(fù)用方案,這里是 epoll ,在 epoll 中相應(yīng) socket 的就緒的是 set_result() 得到調(diào)用的根本原因。而這個(gè)就緒事件的傳遞,離不開 Tornado 內(nèi)建的 IOStream ,異步 TCPClient 、異步 HTTPConnection ,這些類的存在為我們隱藏了簡(jiǎn)單調(diào)用后的復(fù)雜性。因此當(dāng)我們?cè)谟?/span> yield 返回耗時(shí)操作時(shí),如果不是 Tornado 的內(nèi)建組件,則必須自己負(fù)責(zé)設(shè)計(jì) set_result 的方案,比如以下代碼:
  @gen.coroutinedef add(self, a, b):
  future = Future()
  def callback(a, b):
  print("calculating the sum of %d + %d:" % (a,b))
  future.set_result(a+b)
  tornado.ioloop.IOLoop.instance().add_callback(callback, a, b)
  result = yield future
  print("%d + %d = %d" % (a, b, result))
  通過手動(dòng)將包含set_result() 的回調(diào)函數(shù)加到 IOLoop 中,于是回調(diào)下一次迭代中執(zhí)行, set_result() 被調(diào)用,協(xié)程恢復(fù)控制權(quán)。
   總結(jié)
  實(shí)現(xiàn)高性能服務(wù)端,同步多進(jìn)程、多線程風(fēng)靡一時(shí),然而由于其需要在內(nèi)核態(tài)進(jìn)行上下文切換,同步時(shí)還要加鎖,導(dǎo)致并發(fā)性能低下。于是異步冒出來了,如Session 、狀態(tài)機(jī),當(dāng)然還有本文討論的協(xié)程。
  學(xué)習(xí)協(xié)程,主要的原因是在看SS 代碼中深受狀態(tài)機(jī)代碼的折磨 ( 為啥要看 SS 的代碼?你懂的 ) 。因?yàn)樵跔顟B(tài)機(jī)中,邏輯代碼被分割成多塊分散在 N 個(gè)回調(diào)里 ( 各種 on_xxxxx) ,割裂了人的順序性思維,在閱讀代碼、理解邏輯時(shí)跳來跳去令人痛不欲生。反觀協(xié)程,真正做到了同步編碼異步執(zhí)行。
  然而深挖協(xié)程的實(shí)現(xiàn)發(fā)現(xiàn),協(xié)程的高性能和易編寫易閱讀是以后端框架復(fù)雜的封裝為代價(jià)的。即使是在Python 中我們擁有能夠維護(hù)上下文的生成器,為了實(shí)現(xiàn)協(xié)程的調(diào)度, Tornado 依然耗費(fèi)了不少功夫:從定義 Future 對(duì)象用于等待結(jié)果返回,到使用 coroutine 裝飾器將生成器的 yield 返回值封裝成 Runner ,最后到 set_result Runner 重新跑起來,而這些都依賴于 IOLoop 的調(diào)度。在經(jīng)過層層跳轉(zhuǎn)后達(dá)成了協(xié)程的調(diào)度目的,不得不感慨 Tornado 設(shè)計(jì)的巧妙。
  不管怎么說,對(duì)于我等碼農(nóng)來說,協(xié)程代碼寫起來真的爽,讀起來也也很爽,可以少死很多腦細(xì)胞,這就夠了。

來源:binsite
您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?