創(chuàng)建線程
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_function)(void *),void *restrict arg)
//成功返回0,其他值則出錯
// 第一個參數(shù)為指向線程標(biāo)識符的指針.第二個參數(shù)用來設(shè)置線程的屬性,第三個參數(shù)是線程運行函數(shù)的起始位置,第四個參數(shù)是運行函數(shù)的參數(shù)
extern int pthread_join __p(pthread_t __th,void ** __thread_return);
//第一個參數(shù)為被等待的線程標(biāo)識符,第二個參數(shù)為一個用戶定義的指針,它可以用來存儲被等待線程的返回值.
//這個函數(shù)是一個線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時,被等待線程的資源被收回。如果執(zhí)行成功,將返回0,如果失敗則返回一個錯誤號。
C99新增restrict用于限定指針;該關(guān)鍵字用于告訴編譯器,所有修改該指針?biāo)赶虻膬?nèi)容的操作全部都是基于該指針的,即不存在其它進(jìn)行修改操作的途徑;這樣可以幫助編譯器進(jìn)行更好的代碼優(yōu)化.生成更有效率的匯編代碼.在gcc中使用C99標(biāo)準(zhǔn)要加上 -std=C99;
例如:void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n) 這是一個很有用的內(nèi)存復(fù)制函數(shù),由于兩個參數(shù)都加了restrict限定,所以兩塊區(qū)域不能重疊,即 dest指針?biāo)傅膮^(qū)域,不能讓別的指針來修改,即src的指針不能修改. 相對應(yīng)的別一個函數(shù) memmove(void *dest,const void * src,size_t)則可以重疊。
- 與fork()調(diào)用創(chuàng)建一個進(jìn)程的方法不同,pthread_create()創(chuàng)建的線程并不具備和主線程(即調(diào)用pthread_create()的線程)同樣的執(zhí)行序列,而是使其運行start_routine(arg)函數(shù)。thread返回創(chuàng)建的線程ID,而 attr是創(chuàng)建線程時設(shè)置的線程屬性。pthread_create()的返回值表示線程創(chuàng)建是否成功。盡管arg是void *類型的變量,但它同樣可以作為任意類型的參數(shù)傳給start_routine()函數(shù);同時,start_routine()可以返回一個void *類型的返回值,而這個返回值也可以是其他類型,并由pthread_join()獲取。
- 為了設(shè)置attr屬性,POSIX定義了一系列屬性設(shè)置函數(shù),包括pthread_attr_init()、pthread_attr_destroy()和與各個屬性相關(guān)的pthread_attr_get---/pthread_attr_set---函數(shù)。
線程取消
通常線程會在主體函數(shù)退出的時候自動終止,但是也可以因為收到另外一個線程發(fā)來的終止(取消)請求而強制終止.線程取消的方法是向目標(biāo)線程發(fā)送cancel信號,但是如何處理cancel信號則是由目標(biāo)線程自己決定的,可以忽略,立即終止,或者是繼續(xù)運行到cancelation-point(取消點),由不同的cancelation狀態(tài)決定.線程收到CANCEL信號的缺省狀態(tài)是運行到取消點(pthread_create()創(chuàng)建線程的缺省狀態(tài)).
取消點包括以下一些函數(shù):pthread_join(),pthread_testcancel(),pthread_condition_wait(),pthread_cond_timewait(),sem_wait(),sigwait()等等函數(shù),以及read(),write()等會引起阻塞的系統(tǒng)調(diào)用都是取消點.而其他pthread函數(shù)都不會引起Cancelation動作。但是pthread_cancel的手 冊頁聲稱,由于LinuxThread庫與C庫結(jié)合得不好,因而目前C庫函數(shù)都不是Cancelation-point;但CANCEL信號會使線程從阻 塞的系統(tǒng)調(diào)用中退出,并置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統(tǒng)調(diào)用前后調(diào)用 pthread_testcancel(),從而達(dá)到POSIX標(biāo)準(zhǔn)所要求的目標(biāo),即如下代碼段:
pthread_testcancel();
retcode = read(fd,buffer,length);
pthread_testcancel();
若線程處于無限循環(huán),且沒有執(zhí)行到取消點的必然路徑,則線程無法由外部的其他線程取消請求而終止,因此在這樣的循環(huán)體的必經(jīng)路徑上應(yīng)該加入pthread_testcancel()調(diào)用.
通過
int pthread_cancel(pthread_t tid);發(fā)送終止信號給thread線程,若成功則返回0,否則非0,成功發(fā)送并不意味著thread會終止.(那么檢查pthread_cancel的返回狀態(tài)不是沒有意義么???)int pthread_setcancelstate(int state, int *oldstate):設(shè)置本線程對Cancel信號的反應(yīng),state有兩種值:PTHREAD_CANCEL_ENABLE(缺?。?/code>和PTHREAD_CANCEL_DISABLE,分別表示收到信號后設(shè)為CANCLED狀態(tài)和忽略CANCEL信號繼續(xù)運行;old_state如果不為 NULL則存入原來的Cancel狀態(tài)以便恢復(fù)。int pthread_setcanceltype(int type, int *oldtype)設(shè)置本線程取消動作的執(zhí)行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,僅當(dāng)Cancel狀態(tài)為Enable時有效,分別表示收到信號后繼續(xù)運行至下一個取消點再退出和 立即執(zhí)行取消動作(退出);oldtype如果不為NULL則存入運來的取消動作類型值。void pthread_testcancel(void)檢查本線程是否處于Canceld狀態(tài),如果是,則進(jìn)行取消動作,否則直接返回。
互斥鎖
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- pthread_mutex_t 是posix下抽象出的一個鎖類型的結(jié)構(gòu):pthread_mutex_t.通過對該結(jié)構(gòu)的訪問.顧名思義,加鎖以后,別人就無法打開,只有當(dāng)鎖沒有關(guān)閉的時候才可以訪問資源.使用互斥鎖可以讓線程按順序執(zhí)行.通常,互斥鎖通過確保一次只有一個線程執(zhí)行代碼的臨界段來同步多個線程.互斥鎖還可以保護(hù)單線程代碼.要更改缺省的互斥鎖屬性,可以對屬性對象進(jìn)行聲明和初始化。通常,互斥鎖屬性會設(shè)置在應(yīng)用程序開頭的某個位置,以便可以快速查找和輕松修改。
- pthread_mutex_init()函數(shù)是以動態(tài)方式創(chuàng)建互斥鎖的,參數(shù)attr指定了新建互斥鎖的屬性。如果參數(shù)attr為NULL,則使用默認(rèn)的互斥鎖屬性,默認(rèn)屬性為快速互斥鎖 ?;コ怄i的屬性在創(chuàng)建鎖的時候指定,在LinuxThreads實現(xiàn)中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經(jīng)被鎖定的互斥鎖加鎖時表現(xiàn)不同。函數(shù)成功完成之后會返回0,其他的任何值表示出現(xiàn)錯誤.執(zhí)行成功后,互斥鎖被初始化為鎖住的狀態(tài).
互斥鎖的屬性:
- PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當(dāng)一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,并在解鎖后按優(yōu)先級獲得鎖。這種鎖策略保證了資源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
- PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當(dāng)不允許多次加鎖時不會出現(xiàn)最簡單情況下的死鎖。
- PTHREAD_MUTEX_ADAPTIVE_NP,適應(yīng)鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。
鎖的一些操作
- int pthread_mutxe_lock(pthread_mutex_t *mutex)
- int phread_mutex_unlock(pthread_mutex_t *mutex)
- int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經(jīng)被占據(jù)時返回EBUSY而不是掛起等待。
死鎖
死鎖主要發(fā)生在有多個依賴鎖存在時, 會在一個線程試圖以與另一個線程相反順序鎖住互斥量時發(fā)生. 如何避免死鎖是使用互斥量應(yīng)該格外注意的東西??傮w來講, 有幾個不成文的基本原則:
- 對共享資源操作前一定要獲得鎖。
- 完成操作以后一定要釋放鎖。
- 盡量短時間地占用鎖。
- 如果有多鎖, 如獲得順序是ABC連環(huán)扣, 釋放順序也應(yīng)該是ABC。
- 線程錯誤返回時應(yīng)該釋放它所獲得的鎖。
條件變量
#include <pthread.h>
pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *attr);
//成功返回0,其他的值表示錯誤
不能由多個線程同時初始化一個條件變量。當(dāng)需要重新初始化或釋放一個條件變量時,應(yīng)用程序必須保證這個條件變量未被使用。
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
//返回0表示成功,其他值表示失敗
該函數(shù)將解鎖mutex參數(shù)指向的互斥鎖,并使當(dāng)前線程阻塞在cv參數(shù)指向的條件變量上。被阻塞的線程可以被pthread_cond_signal函數(shù),pthread_cond_broadcast函數(shù)喚醒,也可能在被信號中斷后被喚醒。pthread_cond_wait函數(shù)的返回并不意味著條件的值一定發(fā)生了變化,必須重新檢查條件的值。pthread_cond_wait函數(shù)返回時,相應(yīng)的互斥鎖將被當(dāng)前線程鎖定,即使是函數(shù)出錯返回。一般一個條件表達(dá)式都是在一個互斥鎖的保護(hù)下被檢查。當(dāng)條件表達(dá)式未被滿足時,線程將仍然阻塞在這個條件變量上。當(dāng)另一個線程改變了條件的值并向條件變量發(fā)出信號時,等待在這個條件變量上的一個線程或所有線程被喚醒,接著都試圖再次占有相應(yīng)的互斥鎖。
pthread_cleanup_push()和pthread_cleanup_pop()的目的和作用
例如一個線程thread1:
pthread_mutex_lock(&mutex);
//一些會阻塞程序運行的調(diào)用,比如套接字的accept,等待客戶連接
sock = accept(......); //這里是隨便找的一個可以阻塞的接口
pthread_mutex_unlock(&mutex);
上例中若線程1執(zhí)行了accept(),線程會阻塞(也就是等在那里,有客戶端連接的時候才返回,或則出現(xiàn)其他故障),線程等待中......
這時候線程2發(fā)現(xiàn)線程1等了很久,不賴煩了,他想關(guān)掉線程1,于是調(diào)用pthread_cancel()或者類似函數(shù),請求線程1立即退出。這時候線程1仍然在accept等待中,當(dāng)它收到線程2的cancel信號后,就會從accept中退出,然后終止線程,注意這個時候線程1還沒有執(zhí)行:pthread_mutex_unlock(&mutex);也就是說鎖資源沒有釋放,這回造成其他線程的死鎖問題。
所以必須在線程接收到cancel后用一種方法來保證異常退出(也就是線程沒達(dá)到終點)時可以做清理工作(主要是解鎖方面),pthread_cleanup_push和pthread_cleanup_pop就是用來做這樣的工作的。
pthread_cleanup_push(some_clean_func,...)
pthread_mutex_lock(&mutex);
//一些會阻塞程序運行的調(diào)用,比如套接字的accept,等待客戶連接
sock = accept(......); //這里是隨便找的一個可以阻塞的接口
pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
return NULL;
上面的代碼,如果accept被cancel后線程退出,會自動調(diào)用some_clean_func函數(shù),在這個函數(shù)中你可以釋放鎖資源。如果accept沒有被cancel,那么線程繼續(xù)執(zhí)行,當(dāng)pthread_mutex_unlock(&mutex);表示線程自己正確的釋放資源了,而執(zhí)行pthread_cleanup_pop(0);也就是取消掉前面的some_clean_func函數(shù)。接著return線程就正確的結(jié)束了。
push進(jìn)去的函數(shù)可能在以下三個時機執(zhí)行:
- 顯示的調(diào)用pthread_exit();
- 在cancel點線程被cancel。
- pthread_cleanup_pop()的參數(shù)不為0時。