1. 程式人生 > >IO複用的總結及一些問題

IO複用的總結及一些問題

  在前面的文章中,分別介紹了常用的三種實現IO多路複用的函式:selectpollepoll今天對主要是對這三個函式的總結,以及對一些io複用的問題的總結。

總結

系統呼叫 select poll epoll
事件集合 使用者通過3個引數(readset、writeset,exceptset)分別傳入感興趣的可讀、可寫及異常等事件,核心通過對這些引數的線上修改來反饋其中的就緒事件。這使得使用者每次呼叫select都要重置這3個引數 同一處理所有事件型別,因此只需要一個事件集引數(struct pollfd *fds)。使用者通過fd.events傳入感興趣的事件,核心通過修改pollfd.revents反饋其中就緒的事件 核心通過一個事件表直接管理使用者感興趣的所有事件。因此每次呼叫epoll_wait時,無需反覆傳入使用者感興趣的事件。Epoll_wait系統呼叫的引數events僅用來反饋就緒的事件
應用程式索引就緒檔案描述符的時間複雜度 O(n) O(n) O(1)
最大支援的檔案描述符數 一般有最大值限制:1024 65535 系統允許開啟的最大檔案描述符數目(cat/proc/sys/fs/file-max) 65535 系統允許開啟的最大檔案描述符數目(cat/proc/sys/fs/file-max)
工作模式 LT LT 支援ET高效模式
核心實現和工作效率 採用輪詢的方式來檢測就緒事件,演算法的時間複雜度為O(n) 採用輪詢的方式來檢測就緒事件,演算法的時間複雜度為O(n) 採用回撥的方式來檢驗就緒事件,演算法的事件複雜度為O(1)

注意
  ①、65535,限制的是返回的檔案描述符的最大個數,而不是伺服器允許連線的最大個數,因為伺服器連線時,消耗的是檔案描述符而不是埠號;
  ②、埠號是unsigned short int 型別的,所以埠號的範圍是[0,65536]。繫結-1埠的話,其實繫結的是最大的埠號,因為-1的無符號數表示的就是最大值。當我們繫結0號埠的話,系統隨機返回一個可用的埠號;
  65535個埠限制與意義:

  

對於客戶端來說,是指對外發起不同連線的最大個數;
對於伺服器來說,是指對外提供不同監聽的最大個數。

  ③、select監聽的檔案描述符之所以是1024,是因為select採用使用者態和核心態的雙層輪詢,耗cpu所以不能太大,以2^n儲存,1024正好合適。
  
Q1:IO複用是同步還是非同步?
  關於同步和非同步的概念,以及IO模型,不瞭解的可以參考IO模型。同步和非同步是針對訊息的通知機制而言,同步是需要主動等待訊息通知,而非同步則是被動接收訊息通知,通過回撥、通知、狀態等方式來被動獲取訊息。IO多路複用中(以select為例)阻塞到select函式呼叫時,使用者程序是主動等待並呼叫select函式獲取資料就緒狀態訊息,並且其程序狀態為阻塞。所以,把IO多路複用歸為同步阻塞模式。

Q2:epoll的EPOLLONESHOT事件?
  epoll模型的ET模式一般來說只觸發一次,然而在併發程式中一個socket上的事件還是有可能被觸發多次,例如:當epoll_wait已經檢測到socket描述符fd1就緒,並通知一個執行緒去處理fd1的就緒書劍,在處理過程中該fd1又有新的資料可讀,會喚醒其他執行緒對fd1進行操作,那麼就出現了兩個工作執行緒同時處理同一個socket的情況,這就會引發資料的不一致性等問題。應該是socket在任意連線的時候都只能被同一個執行緒去處理。當然,我們可以通過使用epoll的EPOLLONESHOT事件來實現。
  對於註冊了EPOLLONESHOT事件的檔案描述符,作業系統最多隻觸發其上註冊的一個可讀,可寫,異常事件,且只觸發一次.除非我們使用epoll_ctl函式重置該檔案描述符上註冊的EPOLLONESHOT事件. 所以當一個執行緒去處理某個socket的時候,其他執行緒是沒有機會來操縱該socket的。當該執行緒處理完socket上的事件後,應該重置該socket上的EPOLLONESHOT事件,這樣,當該socket下次可讀時,EPOLLIN事件可以被觸發,從而讓其它的工作執行緒可以去處理該socket。

Q3:為什麼說epoll比select和poll高效?
  ①、select/poll返回時,核心會直接修改出傳入事件集合,用於標示哪些事件就緒。所以每次呼叫時都要傳遞監控的所有socket給select/poll系統呼叫,也就是說要將使用者態的socket列表拷貝到核心態,當監聽的檔案描述符數量上萬的話每次都要拷貝幾十幾百KB的記憶體到核心態,效率很低。而呼叫epoll_wait時不用每次呼叫都向核心拷貝事件描述資訊,它在核心中維護一個事件表,並提供一個獨立的系統呼叫epoll_ctl來控制往其中新增 刪除 修改事件。這樣每次epoll_wait呼叫都直接從該核心事件表中取得使用者註冊的事件,而無須反覆從使用者空間讀取這些事件檔案描述符就會被註冊到核心事件中,只有當有新的socket需要監聽時,才會從使用者空間拷貝。
  ②、select和poll採用的是輪詢的機制,每次都需要將掃描一遍所有的檔案描述符,並從其中找出就緒的檔案描述符返回給使用者。這樣的話時間複雜度是O(n)。而epoll_wait則不同,它採用的是回撥的機制。核心維護了一個雙鏈表,使用者儲存發生的事件;當核心檢測到就緒的檔案描述符後,就會觸發回撥函式,將就緒的檔案描述符對應的事件插入到核心就緒連結串列中。當epoll_wait呼叫時,僅僅觀察這個list連結串列裡有沒有資料。有資料就返回,沒有資料就sleep,等到超時t時間到後即使連結串列沒資料也返回。它的時間複雜度是O(1)。
  ③、select和poll只能工作在低效的LT模式下,epoll支援高效的ET模式,在這種模式下,可以減少epoll_wait的呼叫。並且epoll還支援EPOLLONESHOT事件,該事件能進一步的減少可讀、可寫、異常等事件被觸發的次數。
  
  但是,當活動連線數比較多的情況下,epoll並不一定比select和poll高效,因為在這個時候,回撥函式被觸發的比較頻繁,需要不斷的壓棧出棧操作。epoll用於處理大量連結中,少量活動連線的情況時比較高效。