什麼是驚群 舉一個很簡單的例子,當你往一群鴿子中間扔一塊食物,雖然最終只有一個鴿子搶到食物,但所有鴿子都會被驚動來爭奪,沒有搶到食物的鴿子只好回去繼續睡覺, 等待下一塊食物到來。這樣,每扔一塊食物,都會驚動所有的鴿子,即為驚群。對於作業系統來說,多個進程/線程在等待同一資源是,也會產生類似的效果,其結 果就是每當資源可用,所有的進程/線程都來競爭資源,造成的後果: 1)系統對使用者進程/線程頻繁的做無效的調度、環境切換,系統系能大打折扣。 2)為了確保只有一個線程得到資源,使用者必須對資源操作進行加鎖保護,進一步加大了系統開銷。
最常見的例子就是對於socket描述符的accept操作,當多個使用者進程/線程 監聽在同一個連接埠上時,由於實際只可能accept一次,因此就會產生驚群現象,當然前面已經說過了,這個問題是一個古老的問題,但目前的核心版本已經修複了這個問題,一個連結過來,核心只會喚醒一個子進程出來accept,這樣就不用擔心驚群效應了。
但是對於線程池呢。 一個基本的線程池架構是基於生產者和消費者模型的。生產者往隊列裡面新增工作,而消費者從隊列中取任務並進行執行。一般來說,消費時間比較長,一般有許多個消費者。當許多個消費者同時在等待任務隊列的時候,也就發生了“驚群效應”。
// 線程池類型定義struct thread_pool { int max_threads; // 線程池中最大線程數限制 int curr_threads; // 當前線程池中總的線程數 int idle_threads; // 當前線程池中閒置線程數 pthread_mutex_t mutex; // 線程互斥鎖 pthread_cond_t cond; // 線程條件變數 thread_job *first; // 線程任務鏈表的表頭 thread_job *last; // 線程任務鏈表的表尾 ...} 消費者一般在等待任務或者處理任務的過程中。
pthread_mutex_lock (& pool -> mutex ); while ( pool -> first == NULL ) //如果沒有任務,一直等待 pthread_cond_wait(&pool->cond, &pool->mutex); pthread_mutex_unlock(&pool->mutex); //回調處理 job->func(job->arg);
如果生產者有任務了,那麼就通知消費者,生產者調用下面的代碼,通知有任務了。
pthread_mutex_lock(&pool->mutex);pthread_cond_signal(&pool->cond);pthread_mutex_unlock(&pool->mutex);
注意上面的用法,不是pthread_cond_broadcast,這個是廣播給所有等待任務的消費者,會產生驚群效應。
pthread_cond_signal函數的作用是發送一個訊號給另外一個正在處於阻塞等待狀態的線程,使其脫離阻塞狀態,繼續執行.如果沒有線程處在阻塞等待狀態,pthread_cond_signal也會成功返回。
但使用pthread_cond_signal不會有“驚群現象”產生,他最多隻給一個線程發訊號。假如有多個線程正在阻塞等待著這個條件變數的話,那麼是 根據各等待線程優先順序的高低確定哪個線程接收到訊號開始繼續執行。如果各線程優先順序相同,則 根據等待時間的長短來確定哪個線程獲得訊號。但
無論如何一個pthread_cond_signal調用最多發信一次。 有了上面這段話,我們就有理由相信,線程池並不會產生“驚群效應”。同時,這種方式使用多進程共用資源,等待管道或者其他資源等,提供cpu利用率。
如果有些系統設計比較簡單,pthread_cond_signal還是會廣播訊號,那如何避免驚群呢。
方法一:要想避免如上線程池設計中的驚群問題,在 仍然共用一個線程互斥鎖的條件下, 給每一個消費者線程建立一個線程條件變數,生產者線程在新增工作時, 找到閒置消費者線程, 將任務置入該消費者的任務隊列中,同時 只通知 (pthread_cond_signal) 該消費者的線程條件變數,消費者線程與生產者線程雖然共用相同的線程互斥鎖(因為有全域資源及調用 pthread_cond_wait 所需),但 線程條件變數的通知過程卻是定向通知的,未被通知的消費者線程不會被喚醒,這樣驚群現象也就不會產生了。當然,還有一些設計上的細節需要注意,比如: 當沒有空閑消費者線程時,需要將任務添加進線程池的全域任務隊列中,消費者線程處理完自己的任務後需要查看一下線程池中的全域任務隊列中是否還有未處理的任務。 【大意】:線上程池中,增加一組線程條件變數,對應於每一個線程。增加任務的時候,如果有空閑線程,那麼只通知某一個空閑線程,並且將其置忙。忙與閑,可以通過條件變數來表徵,用一個鏈表表示(類似串連池)。 如果所有線程都忙,那麼就將任務加入全域隊列,並且通知所有消費者(這時驚群是很小的,除非所有線程都剛好同一時刻完成任務,同一時刻爭奪資源,否則只有極少數線程會發生驚群)。
推薦閱讀有詳細的介紹 《pthread_cond_signal與pthread_cond_wait》 【參考資料】 http://www.tuicool.com/articles/2aumqe http://blog.163.com/leyni@126/blog/static/16223010220122611523786/ http://baike.baidu.com/link?url=6x0zTazmBxTYE9ngPt_boKjS8ivdQnRlfhHj-STCnqG9tjKwfCluPsKlq-ASUkdQTPW3XrD8FtyilBaI75GJCK