從IO的角度深入理解Select、Poll、Epoll的區別推理


  近期剛學習IO多路復用的知識,還有看了django和flask框架WSGIServer的源碼,對源碼中使用的selector模塊比較好奇,也就去稍微深入看了一下個方面資料和相關視頻及底層實現,梳理出這篇文章。

 

  一、Python中起高可用socket服務端的常用三種方式

 

  在初始我們寫一個socket服務端, 如果要供多人同時連接使用的話,有幾大方式如在接收消息部分使用多線程,使用協程, 或者是多進程實現socket服務端 。

 

socket客戶端實現, 用於連接測試服務端

import socket
import time

sc = socket.socket()
sc.connect(('127.0.0.1', 8000))

while True:
    sc.send(b'hello word')
    data = sc.recv(1024)
    print(data)
    time.sleep(1)

  

1)多進程實現socket服務端

import socket
from multiprocessing import Process
import time


sc = socket.socket()
sc.bind(('127.0.0.1', 8000))
sc.listen(5)
sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)


def recv_data(conn):
    while True:
        data = conn.recv(1024)
        if data:
            print(data)
            conn.sendall(data.upper())


while True:
    conn, addr = sc.accept()
    if conn:
        Process(target=recv_data, args=(conn,)).start()
    time.sleep(1)

  使用多進程實現socket服務端的優缺點

優點:解決單進程單線程無法多客戶端連接的問題

缺點:開多進程消耗的資源比較大,並且操作系統多進程數量有限制

 

2)多線程實現socket服務端

# 多線程socket服務端

import socket import threading import time sc = socket.socket() sc.bind(('127.0.0.1', 8000)) sc.listen(5) sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) def recv_data(conn): while True: data = conn.recv(1024) if data: print(data) conn.sendall(data.upper()) while True: conn, addr = sc.accept() if conn: threading.Thread(target=recv_data, args=(conn,)).start() time.sleep(1)

  使用多線程實現socket服務端的優缺點

優點: 可以滿足多客戶端連接,實現簡單, 比多進程更小的資源的消耗

缺點: 開多線程耗資源,且線程間的切換有性能消耗,不能無限開

 

3)使用協程實現socket服務端

import time
import socket
import gevent
from gevent import monkey
monkey.patch_all()


sc = socket.socket()

sc.bind(('127.0.0.1', 8000))
sc.listen(5)
sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)


def recv_data(conn):
    while True:
        data = conn.recv(1024)
        if data:
            print(data)
            conn.sendall(data.upper())


while True:
    conn, addr = sc.accept()
    if conn:
        gevent.spawn(recv_data, conn)
    time.sleep(1)

  使用協程實現socket服務端優缺點:

優點: 協程是微線程,多個協程在一個線程內切換,占用資源最少,並且在socket這種IO密集型的服務中效率很高,有時速度優於多線程socket實現

缺點:比起多進程和多線程實現是基本沒有缺點,唯一是無法利用多CPU,在計算密集型服務時吃力

 

以上三種socket服務端的實現方式都存在的缺點是,如果有1W個連接時,單次就會有1W次IO操作,會有操作系統層面的1W次系統調用,會有比較大的系統調用切換的消耗,這就引出我們的IO多路復用。

 

  二、IO多路復用之Select、Poll、Epoll及其區別

 

1)Select和Poll和Epoll的用法

其實Select和Poll的區別不大,唯一區別是Select對有最大連接數限制1024這個數字是可以修改的,而Poll是基於鏈表結構的沒有最大連接數限制。

import selectors
import socket


select = selectors.DefaultSelector()


def recv_data(conn, mask):
    data = conn.recv(1024)
    if data:
        print(data)
        conn.sendall(data.upper())
    else:
        select.unregister(conn)
        conn.close()


def accept(sc, mask):
    conn, addr = sc.accept()
    conn.setblocking(False)
    select.register(conn, selectors.EVENT_READ, recv_data)


sc = socket.socket()
sc.bind(('127.0.0.1', 8000))
sc.listen(5)
sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sc.setblocking(False)

select.register(sc, selectors.EVENT_READ, accept)      # epoll的話相當於向內核開辟一個空間放文件描述符

while True:
    read = select.select(timeout=1)                    # 相當於遍歷文件描述符
    for key, mask in read:
        callback = key.data
        callback(key.fileobj, mask)

  由於我們使用的python自帶的selectors模塊,代碼中 select = selectors.DefaultSelector() 會根據操作系統的不同實例化合適的Select或者Poll或者Epoll。

  我們知道很多時刻操作系統的進程和線程的調度策略為: 時間片輪轉調度,也就是每個線程在時間片內被cpu調度執行,根據這基礎我們進行分析,假設我們有1w個線程並發執行(不是並行哦)這樣單位時間內就會調度操作系統內核交互1W次,也就產生了1W次IO,如果把這1W次IO變成1次IO,那性能是不是提升很多,而select和epoll就是這樣做的,它把這1W個文件描述符(或者理解成調用)放到一個數組或者鏈表中,一次傳遞給操作系統內核,然后內核內的線程去循環這個數組,去執行相應的指令,然后執行完畢后,操作系統用戶態再拿回這個文件描述符數組,然后遍歷取其中的結果,這樣就從1W次IO變成了1次IO了。

 

多線程下的IO模型

 

select和poll下IO圖解

 

  

  我們先說select和epoll的優缺點:

優點: 可以減少操作系統用戶態和內核態IO的次數,統一監控多個IO操作,然后遍歷獲取結果。

缺點: 每次都要傳遞一個大的數組列表, 還有每次都要多數組列表進行遍歷獲得結果

 

所以在上述缺點的情況下Epoll誕生了:

  epoll會在操作系統內核中開辟一個空間,然后每次系統調用就會把新的文件描述,傳遞給內核(只傳遞一次),然后內核會開另一個線程去監控內核中的文件描述符,在有返回結果后,它會結果返回放到另一個空間(文件描述符活躍),此時用戶態只會遍歷活躍狀態的文件描述符,這樣用空間換時間效率提升,主要體現在:內核多個線程並發處理文件描述符,每次只遍歷活躍的文件描述符。

 

 

epoll IO模型

 

 

至此告一段落,后續還需補充挺多東西,如果操作系統的IO知識:

1、操作系統IO知識,什么是用戶態,內核態

2、操作系統進程線程的調度策略

3、操作系統的系統調用、中斷和異常

4、還有select函數底層實現 可以在linux中用man函數調用查看解釋

5、什么是文件描述符,操作系統中一切皆文件

等等一些列操作系統方面的知識

 


免責聲明!

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



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