[apue] epoll 的一些不為人所註意的特性

来源:https://www.cnblogs.com/goodcitizen/archive/2020/06/05/13004694.html
-Advertisement-
Play Games

之前曾經使用 epoll 構建過一個輕量級的 tcp 服務框架: 一個工業級、跨平臺、輕量級的 tcp 網路服務框架:gevent 在調試的過程中,發現一些 epoll 之前沒怎麼註意到的特性。 a) iocp 是完全線程安全的,即同時可以有多個線程等待在 iocp 的完成隊列上; 而 epoll ...


之前曾經使用 epoll 構建過一個輕量級的 tcp 服務框架:

一個工業級、跨平臺、輕量級的 tcp 網路服務框架:gevent

 

在調試的過程中,發現一些 epoll 之前沒怎麼註意到的特性。

a)  iocp 是完全線程安全的,即同時可以有多個線程等待在 iocp 的完成隊列上;

  而 epoll 不行,同時只能有一個線程執行 epoll_wait 操作,因此這裡需要做一點處理,

  網上有人使用 condition_variable + mutex 實現 leader-follower 線程模型,但我只用了一個 mutex 就實現了,

  當有事件發生了,leader 線程在執行事件處理器之前 unlock  這個 mutex,

  就可以允許等待在這個 mutex 上的其它線程中的一個進入 epoll_wait 從而擔任新的 leader。

  (不知道多加一個 cv 有什麼用,有明白原理的提示一下哈)

 

b)  epoll 在加入、刪除句柄時是可以跨線程的,而且這一操作是線程安全的。

  之前一直以為 epoll 會像 select 一像,添加或刪除一個句柄需要先通知 leader 從 epoll_wait 中醒來,

  在重新 wait 之前通過  epoll_ctl 添加或刪除對應的句柄。但是現在看完全可以在另一個線程中執行 epoll_ctl 操作

  而不用擔心多線程問題。這個在 man 手冊頁也有描述(man epoll_wait):

NOTES
       While one thread is blocked in a call to epoll_pwait(), it is possible for  another  thread  to
       add  a  file  descriptor to the waited-upon epoll instance.  If the new file descriptor becomes
       ready, it will cause the epoll_wait() call to unblock.

       For a discussion of what may happen if a file descriptor in an epoll instance  being  monitored
       by epoll_wait() is closed in another thread, see select(2).

 

 c)  epoll 有兩種事件觸發方式,一種是預設的水平觸發(LT)模式,即只要有可讀的數據,就一直觸發讀事件;

  還有一種是邊緣觸發(ET)模式,即只在沒有數據到有數據之間觸發一次,如果一次沒有讀完全部數據,

  則也不會再次觸發,除非所有數據被讀完,且又有新的數據到來,才觸發。使用 ET 模式的好處是,

  不用在每次執行處理器前將句柄從 epoll 移除、在執行完之後再加入 epoll 中,

  (如果不這樣做的話,下一個進來的 leader 線程還會認為這個句柄可讀,從而導致一個連接的數據被多個線程同時處理)

  從而導致頻繁的移除、添加句柄。好多網上的 epoll 例子也推薦這種方式。但是我在親自驗證後,發現使用 ET 模式有兩個問題:

 

  1)如果連接上來了大量數據,而每次只能讀取部分(緩存區限制),則第 N 次讀取的數據與第 N+1 次讀取的數據,

    有可能是兩個線程中執行的,在讀取時它們的順序是可以保證的,但是當它們通知給用戶時,第 N+1 次讀取的數據

    有可能在第 N 次讀取的數據之前送達給應用層。這是因為線程的調度導致的,雖然第 N+1 次數據只有在第 N 次數據

    讀取完之後才可能產生,但是當第 N+1 次數據所在的線程可能先於第 N 次數據所在的線程被調度,上述場景就會產生。

    這需要細心的設計讀數據到給用戶之間的流程,防止線程搶占(需要加一些保證順序的鎖);

  2)當大量數據發送結束時,連接中斷的通知(on_error)可能早於某些數據(on_read)到達,其實這個原理與上面類似,

    就是客戶端在所有數據發送完成後主動斷開連接,而獲取連接中斷的線程可能先於末尾幾個數據所在的線程被調度,

    從而在應用層造成混亂(on_error 一般會刪除事件處理器,但是 on_read 又需要它去做回調,好的情況會造成一些

    數據丟失,不好的情況下直接崩潰)

 

  鑒於以上兩點,最後我還是使用了預設的 LT 觸發模式,幸好有 b) 特性,我僅僅是增加了一些移除、添加的代碼,

  而且我不用在應用層加鎖來保證數據的順序性了。

 

d)  一定要捕捉 SIGPIPE 事件,因為當某些連接已經被客戶端斷開時,而服務端還在該連接上 send 應答包時:

  第一次 send 會返回 ECONNRESET(104),再 send 會直接導致進程退出。如果捕捉該信號後,則第二次 send 會返回 EPIPE(32)。

  這樣可以避免一些莫名其妙的退出問題(我也是通過 gdb 掛上進程才發現是這個信號導致的)。

 

e)  當管理多個連接時,通常使用一種 map 結構來管理 socket 與其對應的數據結構(特別是回調對象:handler)。

  但是不要使用 socket 句柄作為這個映射的 key,因為當一個連接中斷而又有一個新的連接到來時,linux 上傾向於用最小的

  fd 值為新的 socket 分配句柄,大部分情況下,它就是你剛剛 close 或客戶端中斷的句柄。這樣一來很容易導致一些混亂的情況。

  例如新的句柄插入失敗(因為舊的雖然已經關閉但是還未來得及從 map  中移除)、舊句柄的清理工作無意間關閉了剛剛分配的

  新連接(清理時 close 同樣的 fd 導致新分配的連接中斷)……而在 win32 上不存在這樣的情況,這並不是因為 winsock 比 bsdsock 做的更好,

  相同的, winsock 也存在新分配的句柄與之前剛關閉的句柄一樣的場景(當大量客戶端不停中斷重連時);而是因為 iocp 基於提前

  分配的記憶體塊作為某個 IO 事件或連接的依據,而 map 的 key 大多也依據這些記憶體地址構建,所以一般不存在重覆的情況(只要還在 map 中就不釋放對應記憶體)。

 

  經過觀察,我發現在 linux 上,即使新的連接占據了舊的句柄值,它的埠往往也是不同的,所以這裡使用了一個三元組作為 map 的 key:

  { fd, local_port, remote_port }

  當 fd 相同時,local_port 與 remote_port 中至少有一個是不同的,從而可以區分新舊連接。

 

f)  如果連接中斷或被對端主動關閉連接時,本端的 epoll 是可以檢測到連接斷開的,但是如果是自己 close 掉了 socket 句柄,則 epoll 檢測不到連接已斷開。

  這個會導致客戶端在不停斷開重連過程中積累大量的未釋放對象,時間長了有可能導致資源不足從而崩潰。

  目前還沒有找到產生這種現象的原因,Windows 上沒有這種情況,有清楚這個現象原因的同學,不吝賜教啊

 

最後,再亂入一波 iocp 的特性:

iocp 在非同步事件完成後,會通過完成埠完成通知,但在某些情況下,非同步操作可以“立即完成”,

就是說雖然只是提交非同步事件,但是也有可能這個操作直接完成了。這種情況下,可以直接處理得到的數據,相當於是同步調用。

但是我要說的是,千萬不要直接處理數據,因為當你處理完之後,完成埠依舊會在之後進行通知,導致同一個數據被處理多次的情況。

所以最好的實踐就是,不論是否立即完成,都交給完成埠去處理,保證數據的一次性。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一直對async/await存在疑惑,在博客園裡看到了文中提及的博客,感覺講的很好,自己也學習到了。所以進行簡單的摘要 ...
  • 預設情況下所有的Nuget包都會下載到C盤,目前我這邊有幾十個G的大小,這導致我C盤的容量越來越小... 我們可以在Nuget.config中修改package存放路徑,Nuget.config 在C:\Users\{UserName}\AppData\Roaming\NuGet目錄下 預設如下所示 ...
  • 在做介面測試時,經常會碰到請求參數為token的類型,但是可能大部分測試人員對token,cookie,session的區別還是一知半解。為此我查閱大量的資料做瞭如下總結。 此篇文章也許是最全最通俗的關於Token ,Cookie和Session的區別的文章,好好揣摩文章的每一個字,也許你會有更深的 ...
  • C#將DataTable數據導出CSV文件通用方法! //導出按鈕調用導出方法 protected void btnCSV_Click(object sender, EventArgs e) { DataTable dt = ExportData();//獲取datatable數據源 string ...
  • 一:背景 1. 講故事 在我們的一個全記憶體項目中,需要將一家大品牌店鋪小千萬的trade灌入到記憶體中,大家知道trade中一般會有訂單來源,省市區 ,當把這些欄位灌進去後,你會發現他們特別侵蝕記憶體,因為都是字元串類型,不知道大家對記憶體侵蝕性是不是很清楚,我就問一個問題。 Question: 一個空字 ...
  • 背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3. ...
  • 之所以要延遲多少秒做健康狀態檢查是因為,docker運行為容器以後,會立刻把該容器的狀態標記為running狀態,而對於有些初始化比較慢的容器,如果馬上對它做健康狀態檢查,可能是不健康的狀態,這樣一來我們對瞭解容器是否健康就不是很準確了;如果配合某些工具,很可能存在檢測到容器不健康就把該容器刪除,... ...
  • 樹莓派預設是不帶顯示屏的,如果想要查看系統的一些信息,需要使用電腦登錄到樹莓派,或者通過 HDMI 連接外接顯示器查看。這樣做總是有點麻煩,我們可以通過外接一個 OLED 屏來顯示一些關鍵參數或者圖片。本文將詳細介紹操作方法。 OLED 模組介紹 OLED 屏主要有兩種:128×32 和 128×6 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...