Following system colour scheme - Python 增強提案 Selected dark colour scheme - Python 增強提案 Selected light colour scheme - Python 增強提案

Python 增強提案 (Python Enhancement Proposals)

PEP 3153 – 異步 IO 支援

作者:
Laurens Van Houtven <_ at lvh.cc>
狀態:
已取代
類型:
標準軌跡 (Standards Track)
建立日期:
2011 年 5 月 29 日
公告歷史:

被以下文件取代:
3156

目錄

摘要

本 PEP 描述了 Python 標準函式庫中異步 IO 的一種抽象化。

其目標是達成一種抽象化,使其能由多種不同的異步 IO 後端實現,並為函式庫開發者提供一個目標,以便編寫在這些不同後端之間可移植的程式碼。

原理

目前想要在 Python 中編寫異步程式碼的人有幾個選擇:

  • asyncoreasynchat
  • 客製化的方案,通常基於 select 模組
  • 使用第三方函式庫,例如 Twistedgevent

不幸的是,這些選項各有缺點,而本 PEP 試圖解決這些問題。

儘管 asyncore 模組長期以來一直是 Python 標準函式庫的一部分,但它存在根本性的缺陷,這源於其僵化的 API,無法滿足現代異步網路模組的期望。

此外,它的方法過於簡化,無法為開發者提供充分發揮異步網路潛力所需的所有工具。

目前在生產環境中最受歡迎的解決方案是使用第三方函式庫。這些通常提供令人滿意的方案,但這些函式庫之間缺乏相容性,往往使得程式碼庫與所使用的函式庫緊密耦合。

目前不同異步 IO 函式庫之間缺乏可移植性,導致第三方函式庫開發者的大量重複勞動。一個足夠強大的抽象化意味著異步程式碼只需編寫一次,即可隨處使用。

最終的一個附加目標是,讓標準函式庫中線路與網路協議的實現演進為真正的協議實現,而非那種包山包海(包括阻塞式呼叫 recv())的獨立函式庫。這意味著它們可以輕易地被用於同步與異步程式碼。

通訊抽象化

傳輸 (Transports)

傳輸 (Transports) 為從不同類型的連接讀取位元組與寫入位元組提供了統一的 API。在本 PEP 中,傳輸始終是有序、可靠、雙向、面向串流的雙端點連接。這可能是 TCP socket、SSL 連接、管道(具名或其他)、序列埠……它可能在 POSIX 平台上抽象化檔案描述符,在 Windows 上抽象化 Handle,或在特定平台上抽象化其他適當的資料結構。它封裝了使用該平台資料結構的所有具體實現細節,並為應用程式開發者呈現統一的介面。

傳輸與兩者進行對話:一方面是連接的另一端,另一方面是協議。它是特定底層傳輸機制與協議之間的橋樑。它的職責可以被描述為:讓協議只需發送和接收位元組,由傳輸負責處理最終透過線路發送位元組所需的所有魔法。

傳輸的主要功能是將位元組發送到協議,並從底層協議接收位元組。寫入傳輸是使用 writewrite_sequence 方法完成的。後者是一種效能優化,旨在允許軟體利用某些傳輸機制中的特定功能。具體來說,這允許傳輸使用 writev 代替 writesend,即所謂的分散/集中式 IO (scatter/gather IO)。

傳輸可以被暫停和恢復。這會導致它緩衝來自協議的資料,並停止向協議發送接收到的資料。

傳輸也可以被關閉、半關閉和中止。關閉的傳輸將完成向底層機制寫入其佇列中所有資料,然後停止讀取或寫入資料。中止傳輸會立即停止它,關閉連接而不發送任何仍在佇列中的資料。

進一步的寫入操作將導致拋出異常。半關閉的傳輸可能不再被寫入,但仍會接受傳入的資料。

協定 (Protocols)

新用戶可能對協議 (Protocols) 更為熟悉。其術語與您對所謂「協議」的預期一致:大多數人首先想到的協議,如 HTTP、IRC、SMTP……都是將在協議中實現的例子。

協議最簡短實用的定義是:傳輸與其餘應用程式邏輯之間的(通常是雙向的)橋樑。協議從傳輸中接收位元組,並將該資訊轉化為某種行為,通常導致對某個物件的方法呼叫。同樣地,應用程式邏輯呼叫協議上的某些方法,協議將其轉化為位元組並傳達給傳輸。

最簡單的協議之一是基於行的協議,資料由 \r\n 分隔。協議將從傳輸中接收位元組並對其進行緩衝,直到至少有一整行。完成後,它將把這一行傳遞給某個物件。理想情況下,這應使用可呼叫物件或甚至由協議組成的完全獨立物件來完成,但也可以透過子類化來實現(如 Twisted 的 LineReceiver 情況)。對於另一個方向,協議可以有一個 write_line 方法,它會添加所需的 \r\n 並將新的位元組緩衝區傳遞給傳輸。

本 PEP 建議一種廣義的 LineReceiver,稱為 ChunkProtocol,其中「chunk」(區塊) 是串流中的一條訊息,由指定的分隔符分隔。實例接收分隔符和一個在接收到一塊資料時將被呼叫的可呼叫物件(與 Twisted 的子類化行為相反)。ChunkProtocol 還有一個與上述 write_line 方法類似的 write_chunk 方法。

為什麼要區分協議與傳輸?

協議與傳輸之間的這種分離往往讓初次接觸的人感到困惑。事實上,標準函式庫本身在許多情況下並沒有做出這種區分,特別是在其提供給使用者的 API 中。

儘管如此,這是一個非常實用的區分。在最壞的情況下,它透過明確的關注點分離簡化了實現。然而,它通常服務於更實用的目的:能夠在不同的傳輸中重複使用協議。

考慮一個簡單的 RPC 協議。相同的位元組可能透過許多不同的傳輸進行轉移,例如管道或 socket。為了協助這一點,我們將協議從傳輸中分離出來。協議只負責讀寫位元組,並不關心最終用於轉移這些位元組的機制。

這也允許協議被輕易地堆疊或嵌套,進一步提高代碼重用率。一個常見的例子是 JSON-RPC:根據規範,它可以同時用於 socket 和 HTTP [1]。在實踐中,它傾向於主要封裝在 HTTP 中。協議-傳輸抽象化讓我們能夠建立一個協議與傳輸的堆疊,讓您可以像使用傳輸一樣使用 HTTP。對於 JSON-RPC,您可能會得到類似這樣的堆疊

  1. TCP socket 傳輸
  2. HTTP 協議
  3. 基於 HTTP 的傳輸
  4. JSON-RPC 協議
  5. 應用程式碼

流量控制

消費者 (Consumers)

消費者 (Consumers) 消費由生產者 (Producers) 生產的位元組。透過與生產者的配合,使流量控制成為可能。

消費者在流量控制中主要扮演被動角色。每當生產者有可用資料時,它們就會被呼叫。接著它們處理資料,並通常將控制權交還給生產者。

消費者通常實現某種緩衝區。它們透過向生產者告知這些緩衝區的當前狀態,使流量控制成為可能。消費者可以指示生產者完全停止生產、暫時停止生產,或在之前被告知暫停的情況下恢復生產。

生產者透過 register 方法向消費者註冊。

生產者 (Producers)

消費者消費位元組,生產者生產位元組。

生產者模仿 Twisted 中的 IPushProducer 介面。雖然也有一個 IPullProducer,但總體而言它的趣味性低得多,因此可能超出了本 PEP 的範圍。

雖然生產者可以被告知完全停止生產,但它們擁有的兩個最重要的方法是 pauseresume。這些通常由消費者呼叫,以表示它是否準備好處理(「消費」)更多資料。消費者與生產者互相合作,使流量控制成為可能。

除了 Twisted 的 IPushProducer 介面之外,生產者還有一個 half_register 方法,當消費者試圖註冊該生產者時,會帶入該消費者參數來呼叫此方法。在大多數情況下,這只是設置 self.consumer = consumer 的情況,但某些生產者在註冊消費者時可能需要更複雜的前置條件或行為。終端使用者不應直接呼叫此方法。

曾考慮過的 API 替代方案

以產生器作為生產者

有人提議使用產生器 (Generators) 來實現生產者。然而,這似乎存在一些問題。

首先是概念上的問題。從某種意義上說,產生器是「被動的」。它需要透過方法呼叫被告知要採取行動。而生產者是「主動的」:它發起這些方法呼叫。真實的生產者與其消費者具有對稱關係。在由產生器轉化的生產者的情況下,只有消費者會持有引用,而生產者對消費者的存在一無所知。

這個概念上的問題也轉化為一些技術問題。在其消費者上的 write 方法呼叫成功後,(push) 生產者可以再次採取行動。在產生器的情況下,它需要被告知,要麼是透過疊代協議要求下一個物件(這個過程可能會無限期阻塞),要麼是透過向其拋出某種信號異常。

這種信號傳遞設置可能提供技術上可行的解決方案,但仍不盡人意。一來,這在消費者中引入了無謂的複雜性,消費者現在不僅需要了解如何接收與處理資料,還需要了解如何請求新資料,並處理沒有新資料可用的情況。

後者這種邊緣情況尤其成問題。必須妥善處理,因為整個操作不允許阻塞。然而,產生器在疊代時不能在不終止的情況下引發異常,否則會丟失產生器的狀態。因此,必須使用哨兵值 (sentinel value) 來表示缺乏可用資料,而不是使用異常機制。

最後但同樣重要的一點是,沒有人提供實際可運行的程式碼來演示它們該如何被使用。

參考文獻


來源:https://github.com/python/peps/blob/main/peps/pep-3153.rst

最後修改時間:2025-02-01 08:59:27 GMT