Python Select 解析


首先列一下,sellect、poll、epoll三者的區別 
select 
select最早於1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組(在linux中一切事物皆文件,塊設備,socket連接等。),當select()返回后,該數組中就緒的文件描述符便會被內核修改標志位(變成ready),使得進程可以獲得這些文件描述符從而進行后續的讀寫操作(select會不斷監視網絡接口的某個目錄下有多少文件描述符變成ready狀態【在網絡接口中,過來一個連接就會建立一個'文件'】,變成ready狀態后,select就可以操作這個文件描述符了)。

【socketserver是通過多線程來處理多個請求,每個連接過來分配一個線程來處理,但是select是單進程的,一個進程執行代碼肯定就是串行的,但是現在就要通過一個進程來實現並發的效果,一個進程下只有一個主線程,也就說說用一個線程實現並發的效果。為什么要用一個進程實現多並發而不采用多線程實現多並發呢?==========答:因為一個進程實現多並發比多線程是實現多並發的效率還要高,因為啟動多線程會有很多的開銷,而且CPU要不斷的檢查每個線程的狀態,確定哪個線程是否可以執行。這個對系統來說也是有壓力的,用單進程的話就可以避免這種開銷和給系統帶來的壓力,那么單進程是如何實現多並發的呢???========答:很巧妙的使用了生產者和消費者的模式(異步),生產者和消費者可以實現非阻塞,一個socketserver通過select接收多個連接過來(之前的socket一個進程只能接收一個連接,當接收新的連接的時候產生阻塞,因為這個socket進程要先和客戶端進行通信,二者是彼此互相等待的【客戶端發一條消息,服務端收到,客戶端等着返回....服務端等着接收.........】一直在阻塞着,這個時候如果再來一個連接,要等之前的那個連接斷了,這個才可以連進來。-----------也就是說用基本的socket實現多進程是阻塞的。為了解決這個問題采用每來一個連接產生一個線程,是不阻塞了,但是當線程數量過多的時候,對於cpu來說開銷和壓力是比較大的。)對於單個socket來說,阻塞的時候大部分的時候都是在等待IO操作(網絡操作也屬於IO操作)。為了避免這種情況,就出現了異步=============客戶端發起一個連接,會在服務端注冊一個文件句柄,服務端會不斷輪詢這些文件句柄的列表,主進程和客戶端建立連接而沒有啟動線程,這個時候主進程和客戶端進行交互,其他的客戶端是無法連接主進程的,為了實現主進程既能和已連接的客戶端收發消息,又能和新的客戶端建立連接,就把輪詢變的非常快(死循環)去刷客戶端連接進來的文件句柄的列表,只要客戶端發消息了,服務端讀取了消息之后,有另一個列表去接收給客戶端返回的消息,也不斷的去刷這個列表,刷出來后返回給客戶端,這樣和客戶端的這次通信就完成了,但是跟客戶端的連接還沒有斷,但是就進入了下一次的輪詢。。。。。。。。。。。】

 

 

 

 

 

select目前幾乎在所有的平台上支持,其良好跨平台支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。

select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。

另外,select()所維護的存儲大量文件描述符的數據結構,隨着文件描述符數量的增大,其復制的開銷也線性增長。同時,由於網絡響應時間的延遲 使得大量TCP連接處於非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。

poll 
poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。

poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。

另外,select()和poll()將就緒的文件描述符告訴進程后,如果進程沒有對其進行IO操作,那么下次調用select()和poll() 的時候將再次報告這些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱為水平觸發(Level Triggered)。

epoll 
直到Linux2.6才出現了由內核直接支持的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認為Linux2.6下性能最好的多路I/O就緒通知方法。

epoll可以同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有采取行動,那么它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當復雜。

epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表 就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這里也使用了內存映射(mmap)技術,這樣便徹底省掉了 這些文件描述符在系統調用時復制的開銷。

另一個本質的改進在於epoll采用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描 述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會采用類似callback的回調 機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM