1. 程式人生 > >Linux多執行緒併發總結

Linux多執行緒併發總結

現代作業系統都支援多執行緒併發執行,尤其在多核cpu上,可以真正實現並行執行,而且多執行緒程式設計也利於code的設計,優化架構。Linux系統程式設計手冊29章開始介紹了執行緒的概念。Linux系統提供了兩種執行緒的實現:LinuxThreads和NPTL(Native POSIX Threads Library),第一種比較古老,現在已經基本不支援,現在系統的實現是使用NPTL。
除了linux系統提供的執行緒支援外,標準庫利用linux系統的支援,提供更高階的執行緒操作,比如async(),future,thread,promise等機制,以供開發者靈活運用。下面對各種實現進行總結。
注意一點,不要將執行緒和訊號混合使用,多執行緒程式應該避免使用訊號。如果非要使用的話,最簡單的方法是所有執行緒多阻塞訊號,單獨建立一個專門的執行緒來呼叫sigwait()函式(或者其他類似函式)來接收處理訊號。

1.Linux系統的執行緒實現:

1.1 在Linux中,通過clone系統呼叫來實現執行緒。同一個程序的不同執行緒之間共享全域性變數,堆變數,程序ID(pid)和父程序ID(ppid),程序組ID和會話ID,使用者ID和組ID,開啟的檔案描述符,訊號(signal)處置,fcntl建立的記錄鎖,檔案系統的相關資訊,cpu消耗和資源消耗,nice值。
1.2 執行緒獨有的資訊為:執行緒ID(tid),訊號掩碼,執行緒特有的資料,備選訊號棧,errno變數,浮點型環境,實時排程策略和優先順序,cpu親和力,能力,棧。
1.3在Linux平臺上編譯呼叫了Pthread API的程式時,需要設定cc -pthread編譯選項來支援。
1.4執行緒操作函式:

#include <pthread.h>
/*return 0 on success, or a positive error number on error.
新執行緒會呼叫嗲有引數arg的函式start(start(arg))開始執行。attr設定為null,會使用預設屬性。*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *), void *arg);
/*執行緒id會返回給pthread_create()的呼叫者,一個執行緒可以通過pthread_self()來獲取自己的執行緒*/
pthread_t pthread_self(void);
/*檢查兩個執行緒id
是否相同,return nonzero value if t1 and t2 are equal, otherwise 0.不能直接用==來檢測,因為不同的系統,該型別有不同的實現。Linux中的實現可以用(long)強制轉化為%ld資料。*/ int pthread_equal(pthread_t t1, pthread_t t2); /*retval若非空,則會儲存執行緒終止時返回值的拷貝,該返回值亦即執行緒呼叫return或pthread_exit()時所指定的值。如果傳入一個已經連線過的執行緒id,則結果未知。建立的執行緒要嘛join,要嘛detach,不然,該執行緒退出後會稱為殭屍執行緒,佔用系統資源。join函式會一直阻塞,等待該join的執行緒退出才返回。*/ int pthread_join(pthread_t thread, void **retval); /*如果對執行緒的執行結果不關心,則可以detach,一旦detach,就不能對其呼叫join了,系統線上程終止時會自動清除。*/ int pthread_detach(pthread_t thread); /*執行緒退出有兩種方法,一種執行緒的main函式執行完畢,另外一種可以呼叫exit函式,join函式會得到exit時設定的retval。*/ void pthread_exit(void *retval); /*其他執行緒可以向某一執行緒傳送取消請求,請求其立即退出,比如一旦某個執行緒發生錯誤,會要求其他執行緒一起退出,就可以呼叫該介面,發出請求後,該函式立即返回,不會等待目標執行緒的退出。.return 0 on success, or a positive number on error.目標執行緒會發生什麼,取決於下面介紹的執行緒取消狀態和型別。*/ int pthread_cancel(pthread_t thread); /*該函式設定取消狀態,PTHREAD_CANCEL_DISABLE:執行緒不可取消,cancel請求會被掛起,直到狀態變為PTHREAD_CANCEL_ENABLE. oldstate會返回原來的state。*/ int pthread_setcanclestate(int state, int *oldstate); /*如果執行緒可以取消(enable),則會看type:PTHREAD_CANCEL_ASYNCHRONOUS :非同步取消,就是會在任何時間點取消執行緒,很少用。 PTHREAD_CANCEL_DEFERED :延後取消,直到到達取消點才會取消,這個是預設type。取消點就是很多系統函式,大部分block的函式都是取消點。被取消的函式被呼叫join會返回一個特殊值:PTHREAD_CANCELED.*/ int pthread_setcanceltype(int type, int *oldtype); /*如果code中沒有上面所說的取消點,則可以設定取消點,利用下面函式*/ void pthread_testcancel(void); /*如果某個執行緒被突然取消,則可能很多資源沒有被釋放,則可以設定清理函式,執行緒被取消後會自動呼叫. 最先被push的最後執行,如果程式執行到某個地方不需要執行某個清理函式,則可以pop出來,如果pop引數為0,則該清理函式不會執行,如果引數大於0,則pop出來的清理函式還是會執行。因為這兩個函式可能為巨集定義,則必須在同一個作用域上。如果執行緒是呼叫pthread_exit退出的,則會執行未pop的清理函式,如果是正常return,則不會執行清理函式。code例子可以參考linux系統程式設計手冊P557*/ void pthread_cleanup_push(void (*routine)(void*), void *arg); void pthread_cleanup_pop(int execute); /*訊號動作屬於程序層面,某個執行緒設定的訊號處理函式就是整個程序的訊號處理函式,當某個程序收到訊號可以被該程序的任意執行緒獲取,並呼叫指定的訊號處理函式。但有些訊號可以屬於執行緒例如可以用下面函式向同一個程序的執行緒傳送訊號。return 0 on success, on error, it returns an positive error number.*/ #include <signal.h> int pthread_kill(pthread_t thread, int sig); /*訊號掩碼也是屬於執行緒的,某執行緒建立的子執行緒會繼承父執行緒的訊號掩碼。 return 0 on success, or a positive error number on error. */ int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

一般不要將訊號與執行緒混在一起使用,如果需要使用,建議在建立任何其他執行緒之前,由主執行緒阻塞這些訊號,後續建立的每個執行緒都會繼承主執行緒的訊號掩碼的拷貝,然後再建立一個專用執行緒,修改其sigmask,enable訊號接收,呼叫sigwait()等函式來接收訊號並處理。

2.標準庫std的執行緒實現:

標準庫利用系統的底層實現,提供了一些高階介面,提供一些靈活的非同步呼叫。總結如下:
2.1 高階介面:async() 和 Future
std::async() : 讓一個可被呼叫的object(包括函式,成員函式等)在後臺執行,成為一個獨立執行緒。
class std::future<> 允許等待執行緒結束並獲取其結果(一個返回值或者也許是一個異常)