線程的基本函數 多線程
1.線程建立:併發
#include <pthread.h>ide
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);函數
參數說明:高併發
thread:指向pthread_create類型的指針,用於引用新建立的線程。post
attr:用於設置線程的屬性,通常不須要特殊的屬性,因此能夠簡單地設置爲NULL。spa
*(*start_routine)(void *):傳遞新線程所要執行的函數地址。線程
arg:新線程所要執行的函數的參數。指針
調用若是成功,則返回值是0,若是失敗則返回錯誤代碼。orm
能夠調用pthread_self( )獲取當前線程的id
2.線程終止
若是須要只終止某個線程而不終止整個進程,能夠有三種方法:
從線程函數return。這種方法對主線程不適用,從main函數return至關於調用exit。
一個線程能夠調用pthread_cancel終止同一進程中的另外一個線程。
線程能夠調用pthread_exit終止本身。
#include <pthread.h>
void pthread_exit(void *retval);
參數說明:
retval:返回指針,指向線程向要返回的某個對象。
線程經過調用pthread_exit函數終止執行,並返回一個指向某對象的指針。注意:毫不能用它返回一個指向局部變量的指針,由於線程調用該函數後,這個局部變量就不存在了,這將引發嚴重的程序漏洞。
3.線程等待
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
參數說明:
th:將要等待的張璐,線程經過pthread_create返回的標識符來指定。
thread_return:一個指針,指向另外一個指針,然後者指向線程的返回值。
有關分離線程
在任何一個時間點上,線程是可結合的(joinable)或者是分離的(detached)。一個可結合的線程可以被其餘線程收回其資源和殺死。在被其餘線程回收以前,它的存儲器資源(例如棧)是不釋放的。相反,一個分離的線程是不能被其餘線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。
默認狀況下,線程被建立成可結合的。爲了不存儲器泄漏,每一個可結合線程都應該要麼被顯示地回收,即調用pthread_join;要麼經過調用pthread_detach函數被分離。若是一個可結合線程結束運行但沒有被join,則它的狀態相似於進程中的Zombie Process,即還有一部分資源沒有被回收,因此建立線程者應該調用pthread_join來等待線程運行結束,並可獲得線程的退出代碼,回收其資源。因爲調用pthread_join後,若是該線程沒有運行結束,調用者會被阻塞。
線程同步與互斥
A.mutex (互斥量)
原子操做:要麼都執行,要麼都不執行
對於多線程的程序,訪問衝突的問題是很廣泛的,解決的辦法是引入互斥鎖(Mutex,MutualExclusiveLock),得到鎖的線程能夠完成「讀-修改-寫」的操做,而後釋放鎖給其它線程,沒有得到鎖的線程只能等待而不能訪問共享數據,這樣「讀-修改-寫」三步操做組成一個原子操做,要麼都執行,要麼都不執行,不會執行到中間被打斷,也不會在其它處理器上並行作這個操做。Mutex用pthread_mutex_t類型的變量表示,能夠這樣初始化和銷燬:
返回值:成功返回0,失敗返回錯誤號。
pthread_mutex_init函數對Mutex作初始化,參數attr設定Mutex的屬性,若是attr爲NULL則表示缺省屬性,本章不詳細介紹Mutex屬性,感興趣的讀者能夠參考[APUE2e]。用pthread_mutex_init函數初始化的Mutex能夠用pthread_mutex_destroy銷燬。若是Mutex變量是靜態分配的(全局變量或static變量),也能夠用宏定義PTHREAD_MUTEX_INITIALIZER操做能夠用下列函數:
返回值:成功返回0,失敗返回錯誤號。
一個線程能夠調用pthread_mutex_lock得到Mutex,若是這時另外一個線程已經調用pthread_mutex_lock得到了該Mutex,則當前線程須要掛起等待,直到另外一個線程調用pthread_mutex_unlock釋放Mutex,當前線程被喚醒,才能得到該Mutex並繼續執行。若是一個線程既想得到鎖,又不想掛起等待,能夠調用pthread_mutex_trylock,若是Mutex已經被 另外一個線程得到,這個函數會失敗返回EBUSY,而不會使線程掛起等待。
通常狀況下,若是同一個線程前後兩次調用lock,在第二次調用時,因爲鎖已經被佔用,該線程會掛起等待別的線程釋放鎖,然而鎖正是被本身佔用着的,該線程又被掛起而沒有機會釋放鎖,
所以就永遠處於掛起等待狀態了,這叫作死鎖(Deadlock)。另外一種典型的死鎖情形是這樣:線程A得到了鎖1,線程B得到了鎖2,這時線程A調用lock試圖得到鎖2,結果是須要掛起等待線程B釋放鎖2,而這時線程B也調用lock試圖得到鎖1,結果是須要掛起等待線程A釋放鎖1,因而線程A和B都永遠處於掛起狀態了。不難想象,若是涉及到更多的線程和更多的鎖,有沒有可能死鎖的問題將會變得複雜和難以判斷。
B. Condition Variable(條件變量)
返回值:成功返回0,失敗返回錯誤號。
和Mutex的初始化和銷燬相似,pthread_cond_init函數初始化一個Condition Variable,attr參數爲NULL則表示缺省屬性,pthread_cond_destroy函數銷燬一個Condition Variable。若是ConditionVariable是靜態分配的,也能夠用宏定義PTHEAD_COND_INITIALIZER初始化,至關於用pthread_cond_init函數初始化而且attr參數爲NULL。Condition Variable的操做能夠用下列函數:
返回值:成功返回0,失敗返回錯誤號。
可見,一個Condition Variable老是和一個Mutex搭配使用的。一個線程能夠調用pthread_cond_wait在一個Condition Variable上阻塞等待,這個函數作如下三步操做:
1. 釋放Mutex
2. 阻塞等待
3. 當被喚醒時,從新得到Mutex並返回
c. Semaphore(信號量)
Mutex變量是非0即1的,可看做一種資源的可用數量,初始化時Mutex是1,表示有一個可用資源,加鎖時得到該資源,將Mutex減到0,表示再也不有可用資源,解鎖時釋放該資源,將Mutex從新加到1,表示又有了一個可用資源。semaphore變量的類型爲sem_t,sem_init()初始化一個semaphore變量,value參數表示可用資源的數量,pshared參數爲0表示信號量用於同一進程的線程間同步,這裏只介紹這種狀況。在用完semaphore變量以後應該調用sem_destroy()釋放與semaphore相關的資源。
調用sem_wait()能夠得到資源(P操做),使semaphore的值減1,若是調用sem_wait()時semaphore的值已是0,則掛起等待。若是不但願掛起等待,能夠調用sem_trywait()。調用sem_post()能夠釋放資源(V操做),使semaphore的值加1,同時喚醒掛起等待的線程。
示例:
生產者-消費者模型,基於固定大小的環形隊
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <signal.h> #include <string.h> #include <semaphore.h> #define _SEM_PRO_ 64 #define _SEM_CON_ 0 sem_t sem_product; sem_t sem_consume; pthread_mutex_t con_lock; pthread_mutex_t pro_lock; int bank[_SEM_PRO_]; void* producter(void* _val) { int index_pro = 0; while(1) { sem_wait(&sem_product); pthread_mutex_lock(&pro_lock); int _product = rand()%1000; bank[index_pro] = _product; printf("product1 done...val is : %d \n", _product); pthread_mutex_unlock(&pro_lock); sem_post(&sem_consume); ++index_pro; index_pro = index_pro%_SEM_PRO_; //sleep(1); } } void* producter2(void* _val) { int index_pro = 0; while(1) { sem_wait(&sem_product); pthread_mutex_lock(&pro_lock); int _product = rand()%1000; bank[index_pro] = _product; printf("product2 done...val is : %d \n", _product); pthread_mutex_unlock(&pro_lock); sem_post(&sem_consume); ++index_pro; index_pro = index_pro%_SEM_PRO_; //sleep(1); } } void* consumer(void* _val) { int index_con = 0; while(1) { sem_wait(&sem_consume); pthread_mutex_lock(&con_lock); int _consume = bank[index_con]; printf("consume1 done...val is : %d\n", _consume); pthread_mutex_unlock(&con_lock); sem_post(&sem_product); ++index_con; index_con = index_con%_SEM_PRO_; //sleep(1); } } void* consumer2(void* _val) { int index_con = 0; while(1) { sem_wait(&sem_consume); pthread_mutex_lock(&con_lock); int _consume = bank[index_con]; printf("consume2 done...val is : %d\n", _consume); pthread_mutex_unlock(&con_lock); sem_post(&sem_product); ++index_con; index_con = index_con%_SEM_PRO_; //sleep(1); } } void run_product_consume() { pthread_t tid_pro; pthread_t tid_con; pthread_t tid_con2; pthread_t tid_pro2; pthread_create(&tid_pro, NULL, producter, NULL); pthread_create(&tid_con, NULL, consumer, NULL); pthread_create(&tid_pro2, NULL, producter2, NULL); pthread_create(&tid_con2, NULL, consumer2, NULL); pthread_join(tid_pro, NULL); pthread_join(tid_con, NULL); pthread_join(tid_pro2, NULL); pthread_join(tid_con2, NULL); } void destroy_all_sem() { printf("process done...\n"); sem_destroy(&sem_product); sem_destroy(&sem_consume); pthread_mutex_destroy(&con_lock); pthread_mutex_destroy(&pro_lock); exit(0); } void init_all_sem() { signal(2, destroy_all_sem); memset(bank, 0, sizeof(bank)); sem_init(&sem_product, 0, _SEM_PRO_); sem_init(&sem_consume, 0, _SEM_CON_); pthread_mutex_init(&con_lock, NULL); pthread_mutex_init(&pro_lock, NULL); } int main() { init_all_sem(); run_product_consume(); return 0; }
d. 讀寫鎖
在編寫多線程的時候,有些公共數據修改的機會比較少。相比較改寫,它們讀的機會反而高的多。一般而言,在讀的過程當中,每每伴隨着查找的操做,中間耗時很長。給這種代碼段加鎖,會極大地下降咱們程序的效率。所以,讀寫鎖有派上了用場。
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分紅讀者和寫者,讀者只對共享資源進行讀訪問,寫者則須要對共享資源進行寫操做。這種鎖相對於自旋鎖而言,能提升併發性,由於在多處理器系統中,它容許同時有多個讀者來訪問共享資源,最大可能的讀者數爲實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。