標籤:org queue def 示範 不同的 描述 rac oba 如何
目錄
- 0. 參考地址
- 1. 前言
- 2. 核心類
- 3. SelectSelector核心函數程式碼分析
- 4. 別名
- 5. 總結
- 6. 代碼報錯問題
- 1. 檔案描述符數量
- 2. 監聽列表是否可以為空白
- 7. 關係圖
0. 參考地址
基本介紹 https://www.cnblogs.com/yinheyi/p/8127871.html
實驗示範 https://www.cnblogs.com/xybaby/p/6406191.html#_label_2
詳細講解 http://aju.space/2017/07/31/Drive-into-python-asyncio-programming-part-1.html
官方文檔selecotors https://docs.python.org/3/library/selectors.html
官方文檔select https://docs.python.org/3/library/select.html
1. 前言
並發的解決方案中,因為阻塞IO調用的原因,同步模型(串列/多進程/多線程)並不適合大規模高並發.在非阻塞IO調用中,我們可以使用一個線程完成高並發的功能,不過因為非阻塞IO會立即返回,如何判斷IO準備就緒以及就緒之後如何處理就變成了關鍵,所以我們需要附帶額外的處理.
不論使用哪一種額外處理方式,核心都是為了獲知IO準備就緒且執行對應的操作,額外處理方式之一就是回調+事件迴圈.
OS已經為我們提供了select/poll/epoll/kqueue等多種底層作業系統介面用以處理IO準備就緒的通知(即通過OS提供的介面可以方便的編寫事件迴圈).而程式還需要完成:如何在IO準備就緒的時候執行預定的操作.
selecotrs模組,總代碼611行,其中有5個類是同一個層級,只是根據OS的類型而有所不同.模組中還包含大量的注釋,所以核心代碼數量就在100行左右.selectors模組為我們提供了非同步編程中的回調模型(後面還會寫非同步編程中的協程模型),所以我覺得對此模組的研究是很有必要的.
2. 核心類
selectors模組中的核心類如下:
BaseSelector:是一個抽象基類,定義了核心子類的函數介面.BaseSelector類定義的核心介面如下:
@abstractmethodregister(self, fileobj, events, data=None) # 提供檔案對象的註冊@abstractmethodunregister(self, fileobj) # 登出登入的檔案對象@abstractmethodselect(self, timeout=None) # 向OS查詢準備就緒的檔案對象
其中,前兩個函數封裝了檔案對象,並提供了data變數用於儲存附加資料,這就提供了回調的環境.第三個函數select是對OS底層select/poll/epoll介面的封裝,用以提供一個統一的對外介面.
_BaseSelectorImpl:是一個實現了register和unregister的基類,注意,此基類並沒有實現select函數,因為select函數在不同OS上使用的底層介面不同,所以應該在對應的子類中定義
SelectSelector:使用windows時的介面
EpollSelector:使用linux時的介面(其他3個類相似,只是應用於不同的OS)
DefaultSelector:此為類別名,selectors模組會根據所在作業系統的類型,選擇最優的介面
如下只對selectselector類的核心代碼進行分析,其他對應類的代碼邏輯基本一致.
3. SelectSelector核心函數程式碼分析
有名元祖selectorkey
SelectorKey = namedtuple(‘SelectorKey‘, [‘fileobj‘, ‘fd‘, ‘events‘, ‘data‘])
此對象是一個有名元祖,可以認為是對檔案對象fileobj,對應的描述符值fd,對應的事件events,附帶的資料data這幾個屬性的封裝.此對象是核心操作對象,關聯了需要監控的檔案對象,關聯了需要OS關注的事件,儲存了附帶資料(其實這裡就放的回呼函數)
3.1 註冊
def __init__(self): super().__init__() self._readers = set() # 使用集合處理唯一性 self._writers = set()
首先,建構函式中定義了_readers和_writers變數用於儲存需要監聽的檔案對象的檔案描述符值,並使用集合特性來處理唯一性.
def register(self, fileobj, events, data=None): key = super().register(fileobj, events, data) if events & EVENT_READ: self._readers.add(key.fd) if events & EVENT_WRITE: self._writers.add(key.fd) return key
一般我們使用register作為第一個操作的函數,代表著你需要監聽的檔案對象,以及,當它發生你關注的事件時,你要如何處理.
此函數有3個參數,分別是檔案對象,監聽事件(可讀為1,可寫為2),附帶資料.
fileobj檔案對象是類檔案對象,與平台強相關,在windows上只能是socket,在linux上可以是任何linux支援的檔案對象.
events是一個int類型的值,就是EVENT_ERAD和EVENT_WRITE
data是附帶資料,我們可以把回呼函數放在這裡
此函數返回的key就是一個selectorkey有名元祖
register函數將使用者監聽的檔案對象和事件註冊到有名元祖中,並加入監聽集合_readers和_writers中
3.2 登出
def unregister(self, fileobj): key = super().unregister(fileobj) self._readers.discard(key.fd) self._writers.discard(key.fd) return key
當我們不需要監聽某一個檔案對象時,使用unregister登出它.這會使得它從_readers和_writers中被彈出.
3.3 查詢
def select(self, timeout=None): timeout = None if timeout is None else max(timeout, 0) ready = [] try: r, w, _ = self._select(self._readers, self._writers, [], timeout) except InterruptedError: return ready r = set(r) w = set(w) for fd in r | w: events = 0 if fd in r: events |= EVENT_READ if fd in w: events |= EVENT_WRITE key = self._key_from_fd(fd) if key: ready.append((key, events & key.events)) return ready
這段代碼描述了使用者向OS發起的查詢邏輯.select函數的timeout參數預設是None,這意味著預設情況下,如果沒有任何一個就緒事件的發生,select調用會被永遠阻塞.
select函數調用底層select/poll/epoll介面,此函數在SelectSelector類和EpollSelector類中的定義有所區別,會根據OS的類型調用對應介面,windows和linux實際調用的底層介面對比如下:
使用者統一調用高層select函數,此函數實際調用的介面為:# windows下使用select(SelectSelector類)r, w, _ = self._select(self._readers, self._writers, [], timeout)# linux下使用epoll(EpollSelector類)fd_event_list = self._epoll.poll(timeout, max_ev)
函數使用ready變數儲存準備就緒的元祖(key, events)
在windows中,一旦底層select介面返回,會得到3個列表,前兩個表示可讀和可寫的檔案對象列表,並使用集合處理為唯一性.準備就緒的元祖對象會加入ready列表中返回.如果定義了timeout不為None,且發生了逾時,會返回一個空列表.
4. 別名
# Choose the best implementation, roughly:# epoll|kqueue|devpoll > poll > select.# select() also can‘t accept a FD > FD_SETSIZE (usually around 1024)if ‘KqueueSelector‘ in globals(): DefaultSelector = KqueueSelectorelif ‘EpollSelector‘ in globals(): DefaultSelector = EpollSelectorelif ‘DevpollSelector‘ in globals(): DefaultSelector = DevpollSelectorelif ‘PollSelector‘ in globals(): DefaultSelector = PollSelectorelse: DefaultSelector = SelectSelector
selectors模組定義了一個別名DefaultSelector用於根據OS類型自動指向最優的介面類.
5. 總結
1 作業系統提供的select/poll/epoll介面可以用於編寫事件迴圈,而selectors模組封裝了select模組,select模組是一個低層級的模組,封裝了select/poll/epoll/kqueue等介面.
2 selectors模組中定義了有名元祖selectorkey,此對象封裝了檔案對象/描述符值/事件/附帶資料,selectorkey為我們提供了回調的環境
3 使用selectors模組可以實現使用回調模型來完成高並發的方案.
4 (非常重要)非同步回調模型,大部分事件和精力都是對回呼函數的設計.回調模型使得每一個涉及IO操作的地方都需要單獨分割出來作為函數,這會分割代碼導致可讀性下降和維護難度的上升.
5 回呼函數之間的通訊很困難,需要通過層層函數傳遞.
6 回調模型很難理解
6. 代碼報錯問題1. 檔案描述符數量
Traceback (most recent call last): File "F:/projects/hello/hello.py", line 119, in <module> loop() File "F:/projects/hello/hello.py", line 102, in loop events = selector.select() File "F:\projects\hello\selectors.py", line 323, in select r, w, _ = self._select(self._readers, self._writers, [], timeout) File "F:\projects\hello\selectors.py", line 314, in _select r, w, x = select.select(r, w, w, timeout)ValueError: too many file descriptors in select()
在windows上,底層使用的是select介面,可以支援的檔案描述符數量理論說是1024,實際測試描述符必須小於512(我的電腦是win10 64bit)
在linux上使用的是epoll,可以支援大於1024的檔案描述符數量,不過測試發現在達到4000左右的時候也會報錯。
stack overflow解釋1:https://stackoverflow.com/questions/31321127/too-many-file-descriptors-in-select-python-in-windows
stack overflow解釋2:
https://stackoverflow.com/questions/47675410/python-asyncio-aiohttp-valueerror-too-many-file-descriptors-in-select-on-win
2. 監聽列表是否可以為空白
Traceback (most recent call last): File "F:/projects/hello/world.py", line 407, in <module> loop() File "F:/projects/hello/world.py", line 378, in loop events = selector.select() File "F:\projects\hello\selectors.py", line 323, in select r, w, _ = self._select(self._readers, self._writers, [], timeout) File "F:\projects\hello\selectors.py", line 314, in _select r, w, x = select.select(r, w, w, timeout)OSError: [WinError 10022] 提供了一個無效的參數。
在windows上,監聽的檔案對象列表不可以為空白:
7. 關係圖
python非同步編程--回調模型(selectors模組)