1. 程式人生 > >5Python全棧之路系列之IO多路復用

5Python全棧之路系列之IO多路復用

技術 多線程 課程 網絡連接 write

Python全棧之路系列之IO多路復用


技術分享


What is IO Multiplexing?

IO多路復用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程。

舉例說明

你是一名老師(線程),上課了(啟動線程),這節課是自習課,學生都在自習,你也在教室裏面坐著,只看著這幫學生,什麽也不幹(休眠狀態),課程進行到一半時,A同學(socket)突然拉肚子,舉手說:老濕我要上廁所(read),然後你就讓他去了,過了一會,B同學(socket)在自習的過程中有個問題不太懂,就請你過去幫她解答下(write),然後你就過去幫他解答了。

上述這種情況就是IO多路復用,你就是一個IO,那麽你解決了A同學的問題和B同學的問題,這就是復用,多路網絡連接復用一個io線程。

與多進程和多線程技術相比,I/O多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。

目前常見支持I/O多路復用的系統調用有 select,poll,epoll,I/O多路復用就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作

What is a select?

select 監視的文件描述符分3類,分別是writefds、readfds和exceptfds,程序啟動後select函數會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為null即可),函數返回,當select函數返回後,可以通過遍歷fdset,來找到就緒的描述符。

技術分享

  • 特點

  1. select最大的缺陷就是單個進程所打開的FD是有一定限制的,它由FD_SETSIZE設置,默認值是1024;

  2. 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低;

  3. 需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大;

Python實現select模型代碼

#!/usr/bin/env python
# _*_coding:utf-8 _*_

import select
import socket

sk1 = socket.socket()
sk1.bind((‘127.0.0.1‘, 8002, ))
sk1.listen()

demo_li = [sk1]
outputs = []
message_dict = {}

while True:
    r_list, w_list, e_list = select.select(sk1, outputs, [], 1)
    
    print(len(demo_li),r_list)
    
    for sk1_or_conn in r_list:
    
        if sk1_or_conn == sk1:
            conn, address = sk1_or_conn.accept()
            demo_li.append(conn)
            message_dict[conn] = []
        else:
            try:
                data_bytes = sk1_or_conn.recv(1024)
                # data_str = str(data_bytes, encoding="utf-8")
                # print(data_str)
                # sk1_or_conn.sendall(bytes(data_str+"good", encoding="utf-8"))
            except Exception as e:
                demo_li.remove(sk1_or_conn)
            else:
                data_str = str(data_bytes, encoding="utf-8")
                message_dict[sk1_or_conn].append(data_str)
                outputs.append(sk1_or_conn)
                
    for conn in w_list:
        recv_str = message_dict[conn][0]
        del message_dict[conn][0]
        conn.sendall(bytes(recv_str+"Good", encoding="utf-8"))
        outputs.remove(conn)

What is a poll?

基本原理:

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項並繼續遍歷,如果遍歷完所有fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。

它沒有最大連接數的限制,原因是它是基於鏈表來存儲的,但是同樣有一個缺點:

  1. 大量的fd的數組被整體復制於用戶態和內核地址空間之間,而不管這樣的復制是不是有意義。

  2. poll還有一個特點是“水平觸發”,如果報告了fd後,沒有被處理,那麽下次poll時會再次報告該fd。

What is a epoll?

poll是在2.6內核中提出的,是之前的select和poll的增強版本。相對於select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。

基本原理:

epoll支持水平觸發和邊緣觸發,最大的特點在於邊緣觸發,它只告訴進程哪些fd剛剛變為就緒態,並且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。

epoll的優點:

  1. 沒有最大並發連接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口)。

  2. 效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會調用callback函數;即Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。

  3. 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。

epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區別如下:

LT模式:當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序並通知此事件。

ET模式:當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序並通知此事件。

LT模式
LT(level triggered)是缺省的工作方式,並且同時支持block和no-block socket。在這種做法中,內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的。

ET模式
ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然後它會假設你知道文件描述符已經就緒,並且不會再為那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少於一定量時導致了一個EWOULDBLOCK 錯誤)。但是請註意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once)。ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

在select/poll中,進程只有在調用一定的方法後,內核才對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。(此處去掉了遍歷文件描述符,而是通過監聽回調的的機制。這正是epoll的魅力所在。)

#Python全棧之路 #Io多路復用


5Python全棧之路系列之IO多路復用