天天看點

python異步程式設計--回調模型(selectors子產品)

 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>​子產品中的核心類如下:

python異步程式設計--回調模型(selectors子產品)

​<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>​上,監聽的檔案對象清單不可以為空:

python異步程式設計--回調模型(selectors子產品)
python異步程式設計--回調模型(selectors子產品)

  以下是一個selectors子產品的服務端代碼示範: