Linux多線程編程小結——乾貨

對於linux下的多線程而言,這裏我們需要區分幾個概念:

1、信號量

2、互斥變量(遞歸和非遞歸)

3、條件變量

4、共享鎖(讀寫鎖)(適用於讀的次數遠大於寫的情況)

 

信號量(sem)相當於是操作系統中PV操作的實現,支持wait和post操作,當信號量的值爲0的時候,wait操作將會阻塞當前線程,而當post操作之後,信號量的值將遞增1,阻塞線程將會恢復運行狀態。信號量不一定是資源的鎖定,也可以是某些計算處理的發生。

 

互斥變量(mutex)強調的是資源的共享使用,當試圖使用資源的時候必須先嚐試獲取互斥變量進行加鎖操作(lock),如果當前互斥變量被其他線程佔用,則阻塞當前線程並等待解鎖,互斥變量不適合於等待某種條件滿足而阻塞等待的情況,因爲等待的時間並無法確定。它包括 4 種操作,分別是創建,銷燬,加鎖和解鎖。

 

條件變量(condition)強調的是對於資源或者某種條件滿足的等待,它需要和互斥變量結合使用,它彌補了互斥變量不確定等待所導致的缺陷問題。它包括5 種操作:創建,銷燬,觸發,廣播和等待。這裏需要注意的是,條件變量本身由互斥量保護,所以在改變條件狀態前必須鎖住互斥量。也就是說,pthread_cond_wait和pthread_cond_timewait必須在mutex的鎖定區域內使用。此外,pthread_cond_signal喚醒等待該條件的某個線程,pthread_cond_broadcast喚醒等待該條件的所有線程,可以認爲,pthread_cond_signal是對pthread_cond_broadcast的優化,signal效率較broadcast高些(When in doubt, broadcast!)。這裏建議在 Linux 平臺上要出發條件變量之前要檢查是否有等待的線程,只有當有線程在等待時纔對條件變量進行觸發。

 

共享鎖(rw_lock)支持讀共享,寫互斥,它適用於讀的情況多於寫的情況。


對於上述的各種同步機制,都有相應的初始化方式,其中包括靜態初始化(宏初始化)和動態初始化(*_init)。

 

需要清楚的是,linux系統中線程程序庫是POSIX pthread,該線程庫是基於C進行的,而我們知道C語言是面向過程的編程語言,而OO思想(面向對象思想)則更適合大部分的程序設計,那麼如何採用C++來實現多線程編程則成爲了一個必須解決的問題。對於多線程的創建,需要提供正確的線程函數的地址,然而,對於C++的類而言,類成員是在類被實例化成爲對象後才存在的,即在編譯時是不存在的,編譯器無法取得函數的確切入口地址,自然無法通過編譯。而爲了解決函數地址的確定問題,可以採用靜態類成員的方式,但是靜態成員只能調用類的其他靜態成員函數,這樣有可能導致所有的類成員最後都成爲靜態成員。值得注意的是,線程函數允許傳入一個參數,這個參數爲解決上述的問題提供了可能性。具體的做法是,線程函數仍未靜態成員,但是傳入的參數則是某個類實例的指針,然後在線程函數裏面調用具體的成員函數,以此來解決靜態成員函數的調用問題。

 

對於多線程的釋放問題,這裏涉及到線程的屬性問題,線程在創建的時候可以指定線程的狀態,包括joinable和detach兩種狀態,對於前者,線程的資源由主線程來負責釋放,並且同一個線程只能被一個線程執行join操作,即等待結束,然後由等待線程負責對線程的資源進行釋放。而對於後者,當線程結束的時候,由操作系統負責線程資源的回收。兩種方式確保在使用多線程的時候避免潛在的內存泄漏問題。需要注意的是,如果設置一個線程的分離屬性,而這個線程運行又非常快,那麼它很可能在pthread_create 函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用,這時調用pthread_create 的線程就得到了錯誤的線程號。而線程的結束有三種方式:一種是線程函數運行結束並返回,一種是線程函數調用pthread_exit退出,最後一種是由其他線程調用線程結束函數pthread_cancel來結束指定線程,而被指定線程則在函數結束點嘗試結束線程活動,這裏的函數結束點通常是sleep函數運行處(延遲取消)。

 

根據對linux內核的學習,進程的創建需要在內核表中創建相應的結構體,任何拷貝父進程的所有資源,這裏的拷貝也只是寫時複製的方式,然後對CPU的一些參數(寄存器的狀態)做必要的調整。而對於線程而言,它則是輕量級的進程,線程按照其調度者可以分爲用戶級線程和核心級線程兩種。不同的線程庫可能採用不同的方式使線程和進程進行對應,包括NPTL(Native POSIX Threading Library)和NGPT(NextGeneration POSIX Threads)。運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啓動一個線程所花費的空間遠遠小於啓動一個進程所花費的空間。

對不同進程來說,它們具有獨立的數據空間,要進行數據的傳遞只能通過通信(進程間的通信)的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接爲其它線程所用,這不僅快捷,而且方便。當然,數據的共享則存在互斥訪問問題以及線程之間的同步問題。

 

對於mutex應該多大的問題,這裏的大小是相對的,如mutex鎖定到解鎖之間的代碼只有一行,比起有10行的就小了。原則是:儘可能大,但不要太大(As big as neccessary, but no bigger)。考慮下面的因素:

1> mutex並不是免費的,是有開銷的,不要太小了,太小了程序只忙於鎖定和解鎖了。

2> mutex鎖定的區域是線性執行的,若太大了,沒有發揮出併發的優越性。

3> 自己掂量1和2,根據實際情況定,或者嘗試着去做。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章