python異步程式設計--回調模型(selectors子產品)
并發的解決方案中,因為阻塞IO調用的原因,同步模型(串行/多程序/多線程)并不适合大規模高并發.在非阻塞IO調用中,我們可以使用一個線程完成高并發的功能,不過因為非阻塞IO會立即傳回,如何判斷IO準備就緒以及就緒之後如何處理就變成了關鍵,是以我們需要附帶額外的處理.
不論使用哪一種額外處理方式,核心都是為了獲知IO準備就緒且執行對應的操作,額外處理方式之一就是回調+事件循環.
<code>OS</code>已經為我們提供了<code>select/poll/epoll/kqueue</code>等多種底層作業系統接口用以處理IO準備就緒的通知(即通過<code>OS</code>提供的接口可以友善的編寫事件循環).而程式還需要完成:如何在IO準備就緒的時候執行預定的操作.
<code>selecotrs</code>子產品,總代碼<code>611</code>行,其中有<code>5</code>個類是同一個級别,隻是根據<code>OS</code>的類型而有所不同.子產品中還包含大量的注釋,是以核心代碼數量就在<code>100</code>行左右.<code>selectors</code>子產品為我們提供了異步程式設計中的回調模型(後面還會寫異步程式設計中的協程模型),是以我覺得對此子產品的研究是很有必要的.
第一部分來源:
2、三個API(select、poll和epoll)的差別和聯系
聯系:
差別:
1.1、select的參數介紹和調用步驟
1.2、缺點:
2 、poll子產品
3、epoll子產品
epoll子產品針對poll的三個缺點的改進
<code>selectors</code>子產品中的核心類如下:
<code>BaseSelector</code>:是一個抽象基類,定義了核心子類的函數接口.<code>BaseSelector</code>類定義的核心接口如下:
其中,前兩個函數封裝了檔案對象,并提供了<code>data</code>變量用于儲存附加資料,這就提供了回調的環境.第三個函數<code>select</code>是對<code>OS</code>底層<code>select/poll/epoll</code>接口的封裝,用以提供一個統一的對外接口.
<code>_BaseSelectorImpl</code>:是一個實作了<code>register</code>和<code>unregister</code>的基類,注意,此基類并沒有實作<code>select</code>函數,因為<code>select</code>函數在不同<code>OS上</code>使用的底層接口不同,是以應該在對應的子類中定義
<code>SelectSelector</code>:使用<code>windows</code>時的接口
<code>EpollSelector</code>:使用<code>linux</code>時的接口(其他<code>3</code>個類相似,隻是應用于不同的<code>OS</code>)
<code>DefaultSelector</code>:此為類别名,<code>selectors</code>子產品會根據所在作業系統的類型,選擇最優的接口
如下隻對<code>selectselector</code>類的核心代碼進行分析,其他對應類的代碼邏輯基本一緻.
有名元祖<code>selectorkey</code>
此對象是一個有名元祖,可以認為是對檔案對象<code>fileobj</code>,對應的描述符值<code>fd</code>,對應的事件<code>events</code>,附帶的資料<code>data</code>這幾個屬性的封裝.此對象是核心操作對象,關聯了需要監控的檔案對象,關聯了需要<code>OS</code>關注的事件,儲存了附帶資料(其實這裡就放的回調函數)
首先,構造函數中定義了<code>_readers</code>和<code>_writers</code>變量用于儲存需要監聽的檔案對象的檔案描述符值,并使用集合特性來處理唯一性.
一般我們使用<code>register</code>作為第一個操作的函數,代表着你需要監聽的檔案對象,以及,當它發生你關注的事件時,你要如何處理.
此函數有<code>3</code>個參數,分别是檔案對象,監聽事件(可讀為<code>1</code>,可寫為<code>2</code>),附帶資料.
<code>fileobj</code>檔案對象是類檔案對象,與平台強相關,在<code>windows</code>上隻能是<code>socket</code>,在<code>linux</code>上可以是任何<code>linux</code>支援的檔案對象.
<code>events</code>是一個<code>int</code>類型的值,就是<code>EVENT_ERAD</code>和<code>EVENT_WRITE</code>
<code>data</code>是附帶資料,我們可以把回調函數放在這裡
此函數傳回的<code>key</code>就是一個<code>selectorkey</code>有名元祖
<code>register</code>函數将使用者監聽的檔案對象和事件注冊到有名元祖中,并加入監聽集合<code>_readers</code>和<code>_writers</code>中
當我們不需要監聽某一個檔案對象時,使用<code>unregister</code>登出它.這會使得它從<code>_readers</code>和<code>_writers</code>中被彈出.
這段代碼描述了使用者向<code>OS</code>發起的查詢邏輯.<code>select</code>函數的<code>timeout</code>參數預設是<code>None</code>,這意味着預設情況下,如果沒有任何一個就緒事件的發生,<code>select</code>調用會被永遠阻塞.
<code>select</code>函數調用底層<code>select/poll/epoll</code>接口,此函數在<code>SelectSelector</code>類和<code>EpollSelector</code>類中的定義有所差別,會根據<code>OS</code>的類型調用對應接口,<code>windows</code>和<code>linux</code>實際調用的底層接口對比如下:
函數使用<code>ready</code>變量儲存準備就緒的元祖<code>(key, events)</code>
在<code>windows</code>中,一旦底層<code>select</code>接口傳回,會得到<code>3</code>個清單,前兩個表示可讀和可寫的檔案對象清單,并使用集合處理為唯一性.準備就緒的元祖對象會加入<code>ready</code>清單中傳回.如果定義了<code>timeout</code>不為<code>None</code>,且發生了逾時,會傳回一個空清單.
<code>selectors</code>子產品定義了一個别名<code>DefaultSelector</code>用于根據<code>OS</code>類型自動指向最優的接口類.
1 作業系統提供的<code>select/poll/epoll</code>接口可以用于編寫事件循環,而<code>selectors</code>子產品封裝了<code>select</code>子產品,<code>select</code>子產品是一個低級别的子產品,封裝了<code>select/poll/epoll/kqueue</code>等接口.
2 <code>selectors</code>子產品中定義了有名元祖<code>selectorkey</code>,此對象封裝了檔案對象/描述符值/事件/附帶資料,<code>selectorkey</code>為我們提供了回調的環境
3 使用<code>selectors</code>子產品可以實作使用回調模型來完成高并發的方案.
4 (非常重要)異步回調模型,大部分事件和精力都是對回調函數的設計.回調模型使得每一個涉及IO操作的地方都需要單獨分割出來作為函數,這會分割代碼導緻可讀性下降和維護難度的上升.
5 回調函數之間的通信很困難,需要通過層層函數傳遞.
6 回調模型很難了解
在<code>windows</code>上,底層使用的是<code>select</code>接口,可以支援的檔案描述符數量理論說是<code>1024</code>,實際測試描述符必須小于<code>512</code>(我的電腦是<code>win10 64bit</code>)
在<code>linux</code>上使用的是<code>epoll</code>,可以支援大于<code>1024</code>的檔案描述符數量,不過測試發現在達到<code>4000</code>左右的時候也會報錯。
<code>stack overflow</code>解釋1:https://stackoverflow.com/questions/31321127/too-many-file-descriptors-in-select-python-in-windows
<code>stack overflow</code>解釋2:
https://stackoverflow.com/questions/47675410/python-asyncio-aiohttp-valueerror-too-many-file-descriptors-in-select-on-win
在<code>windows</code>上,監聽的檔案對象清單不可以為空:
以下是一個selectors子產品的服務端代碼示範: