驚羣筆記

驚羣是什麼微信

當你往一羣鴿子中間扔一塊食物,雖然最終只有一個鴿子搶到食物,但全部鴿子都會被驚動來爭奪。這樣,每扔一塊食物,都會驚動全部的鴿子,即爲驚羣。markdown

對於操做系統而言,驚羣效應是指多進程/線程同時阻塞等待獲取某個資源,若是資源可用,全部等待的線程/進程會被喚起,但最終只有一個進程獲取到資源的控制權,而其餘進程只能從新進入休眠狀態。多線程

驚羣的壞處負載均衡

內核對用戶進程/線程頻繁地作無效的調度、上下文切換等,致使系統性能大打折扣。此外爲確保只有一個進程/線程獲得資源,須要對資源操做進行加鎖保護,加大了系統的開銷,如ngnix的實現socket

Accept驚羣現象tcp

Linux解決accept驚羣的方法是,引入一個排他性標誌位(WQ_FLAG_EXCLUSEVE),將等待進程添加到等待隊列的末尾,當有tcp鏈接完成,就會從半鏈接隊列拷貝socket到鏈接隊列,這個時候咱們就能夠喚醒阻塞的accept了,此時內核喚醒隊列的第一個進程後終止。性能

Epoll驚羣現象優化

在討論 epoll 的驚羣效應時候,須要分爲兩種狀況:spa

一、epoll_create fork 以前建立操作系統

流程:

1. 主進程建立listenfd, 建立epollfd2. 主進程fork多個子進程3. 每一個子進程把listenfd,加到epollfd中4. 當一個鏈接進來時,會觸發epoll驚羣,多個子進程的epoll同時會觸發


accept 驚羣的緣由相似,當有事件發生時,等待同一個文件描述符的全部進程(線程)都將被喚醒,解決思路和 accept 一致,共享一個epollfd, 加鎖或標記解決。

二、epoll_create fork 以後建立

流程:

1. 主進程建立listendfd2. 主進程建立多個子進程3. 每一個子進程建立自已的epollfd4. 每一個子進程把listenfd加入到epollfd中5. 當一個鏈接進來時,會觸發epoll驚羣,多個子進程epoll同時會觸發


每一個子進程的epoll是不一樣的epoll, 雖然listenfd是同一個,但新鏈接過來時, accept會觸發驚羣,但內核不知道該發給哪一個監聽進程,由於不是同一個epoll

Ngnix的解決方案

首先要知道在用戶空間進程間鎖實現的原理,就是能弄一個讓全部進程共享的標誌(因此不能是內存態的),好比mmap的內存,好比文件,而後經過這個東西來控制進程的互斥。

Nginx實現的支持兩種狀況,一種是支持原子操做的狀況,能夠直接使用 mmap,而後lock保存 mmap 的內存區域的地址,一種是不支持原子操做,使用文件鎖來實現 fd共享進程間共享的文件句柄

Ngnix配置了每一個worker進程可以處理的最大鏈接數,當達到最大數的7/8時,該進程不會去爭搶accept鎖,也就不會去處理新鏈接,這也是一種負載均衡。

而對於爭搶鎖的進程,得到鎖不是阻塞過程,都是馬上返回,只有一個進程能獲取到accept_mutex鎖,這樣該進程就能夠調用accept獲取到新鏈接的socketfd

這裏有個優化,獲取鎖以後僅僅設置了一個enents標記,進程並不會立刻accept或者讀取,而是將這個事件保存起來,等待釋放鎖以後,纔會進行accept或操做這個句柄。

對於其餘沒有得到鎖的進程,並不會立刻再去爭搶鎖,而是設置定時器,而後在 epoll休眠,等待下次新鏈接到來。

綜上,當一個鏈接來的時候,此時每一個進程的 epoll 事件列表裏面都是有該 fd 的。搶到該鏈接的進程先釋放鎖,再accept。沒有搶到的進程把該 fd 從事件列表裏面移除,沒必要再調用 accept,形成資源浪費。

同時因爲鎖的控制(以及得到鎖的定時器),每一個進程都能相對公平的 accept 句柄,也就是比較好的解決了子進程負載均衡。

線程池驚羣

多線程中,若是使用條件變量進行生產者與消費者間的同步,當一個線程解鎖並通知其餘線程的時候,就會出現驚羣的現象。

線程調用pthread_cond_signal時,內核會喚醒在相同條件變量上等待的一個或多個線程。若是通知了多個線程,則發生了驚羣。

解決方案:

一、全部線程共用一個鎖,每一個線程有自已的條件變量二、pthread_cond_signal通知時,定向通知某個線程的條件變量,不會出現驚羣

參考連接:https://zhuanlan.zhihu.com/p/51251700

本文分享自微信公衆號 - 機械猿(on_ourway)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索