標籤:

Linux 3.x 中epoll的驚群問題?

3.x內核源碼eventpoll.c中已經有如下代碼,那為什麼還是會發生驚群?
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)
{
....
init_waitqueue_entry(wait, current);
__add_wait_queue_exclusive(ep-&>wq, wait); // **NOTICE**
....
}


就怕這種半截兒問題。

  1. 你說的驚群問題到底是什麼意思?現象是什麼?
  2. 有沒有簡短的示例代碼證明這一問題確實存在?復現步驟是什麼?
  3. 為什麼你認為內核里的這一行代碼應該能解決這一問題?解決之後應該有什麼現象?
  4. 作為提問者,你要先回答清楚上面3個問題,之後,才能問「內核源碼eventpoll.c中已經有如下代碼,那為什麼還是會發生驚群?」


驚不驚群和epoll的這兩句代碼倒沒什麼關係。

既然扯到epoll的代碼實現,那就說說這裡,驚群主要還是指在多線程共享一個epoll fd的時候,如果epoll_wait所等待的fd(比如一條tcp連接可讀)有事件發生時,epoll不知道喚醒哪個線程,就會把所有線程都喚醒,但是最終只有一個線程能去處理,其他的線程都會返回。如果不是多線程共享一個epoll,那就不會有這樣的問題。

你貼這個的意思,是epoll文件系統的一個file_operation里的poll,當wait條件滿足時,喚醒current(它代表正打開該fd、使用這個fd的進程),即喚醒打開這個epoll fd的進程,能喚醒一個進程,也能喚醒所有打開這個fd的進程(抱歉這裡我進程線程不分開講,因為多進程打開一個epoll這種操作,好像不太好用,雖然本質上沒有區別)。所以,一句話,多個進程(或線程)一起wait的時候,會有「驚群」發生。

最後說一句,epoll的實現中,每次epoll_ctl/add/del的時候,通過ep_modify/insert/unlink/remove實現,操作中先調用spin_lock和獲得讀寫鎖,所以在用戶態中epoll是無鎖編程,線程安全的,在內核態中是有鎖的。也就是說,即使驚群,也還是安全的。我認為,這是除了效率外,epoll的最大亮點。


thundering herd problem will never go away, unless the kernel changes socket API.

if you poll() before accept(), kenrel cannot decide which process will accept the incoming connection, so it has to wake up every one.

while poll():
if listening socket is ready:
accept()

get it?


更新下

linux3.0+確實在內核層面上就已經解決了鯨群現象 但nginx作為一個跨平台的服務 必須在自身層面考慮這點。

我所理解的「驚群」現象:

多個進程(當然每個進程都有各自的epoll_fd)把同一個描述符fd加入了自己的epoll中,那麼等到此fd上有事件發生的時候,內核會驅動這多個進程去處理這個事件,這是不科學的。

比如:在accept的時候,如果ABC三個進程都把listenfd加入了自己的epoll中,那麼當客戶端連接來的時候,這三個進程都去accept這個client嗎?顯然會降低效率。

而Nginx裡面很好滴解決了這一難題:

首先啟動進程的時候,不把listenfd加入自己的epoll中,等待進程初始化完畢,開始處理事件的時候,這時候的第一步就是搶鎖,即搶佔對listenfd的控制權,哪個進程搶到,立刻加入自己的epoll。沒搶到的進程繼續自己的處理,但是不會accept。

搶到listenfd的進程,就會accept新的連接。

總之,一把鎖,就很好滴解決了驚群現象

順便說下 這個鎖是 自旋鎖 用原子變數實現的 不會造成進程的睡眠和阻塞。


這段代碼沒毛病,關鍵在於LT(水平觸發)模式。

針對如下情況:

epfd監測listenfd的可讀狀態,多個線程共享這個epfd,並且都阻塞在epoll_wait。

當有新的連接可用時,這段代碼確實只會喚醒一個線程,但是在內核把觸發事件copy到用戶態之前,會對epfd的就緒隊列進行檢查並移除已經觸發的條目,由於epoll默認使用LT(水平觸發)模式,會把移除的條目再次添加進就緒隊列,然後在返回之前再次喚醒等待在該epfd上的線程,直到該可用連接被accept。關鍵代碼如下:


最新內核 已經解決~~

http://blog.csdn.net/mumumuwudi?viewmode=contents


推薦閱讀:

node.js 底層使用的是epoll這種單線程io多路復用,還是多線程阻塞模型?

TAG:Linux | epoll |