多線程編程要使用的函數

創建線程

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_function)(void *),void *restrict arg)
//成功返回0,其他值則出錯
// 第一個參數為指向線程標識符的指針.第二個參數用來設置線程的屬性,第三個參數是線程運行函數的起始位置,第四個參數是運行函數的參數

extern int pthread_join __p(pthread_t __th,void ** __thread_return);
//第一個參數為被等待的線程標識符,第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值.
//這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。如果執行成功,將返回0,如果失敗則返回一個錯誤號。

C99新增restrict用于限定指針;該關鍵字用于告訴編譯器,所有修改該指針所指向的內容的操作全部都是基于該指針的,即不存在其它進行修改操作的途徑;這樣可以幫助編譯器進行更好的代碼優化.生成更有效率的匯編代碼.在gcc中使用C99標準要加上 -std=C99;
例如:void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n) 這是一個很有用的內存復制函數,由于兩個參數都加了restrict限定,所以兩塊區域不能重疊,即 dest指針所指的區域,不能讓別的指針來修改,即src的指針不能修改. 相對應的別一個函數 memmove(void *dest,const void * src,size_t)則可以重疊。

  • 與fork()調用創建一個進程的方法不同,pthread_create()創建的線程并不具備和主線程(即調用pthread_create()的線程)同樣的執行序列,而是使其運行start_routine(arg)函數。thread返回創建的線程ID,而 attr是創建線程時設置的線程屬性。pthread_create()的返回值表示線程創建是否成功。盡管arg是void *類型的變量,但它同樣可以作為任意類型的參數傳給start_routine()函數;同時,start_routine()可以返回一個void *類型的返回值,而這個返回值也可以是其他類型,并由pthread_join()獲取。
  • 為了設置attr屬性,POSIX定義了一系列屬性設置函數,包括pthread_attr_init()、pthread_attr_destroy()和與各個屬性相關的pthread_attr_get---/pthread_attr_set---函數。

線程取消

通常線程會在主體函數退出的時候自動終止,但是也可以因為收到另外一個線程發來的終止(取消)請求而強制終止.線程取消的方法是向目標線程發送cancel信號,但是如何處理cancel信號則是由目標線程自己決定的,可以忽略,立即終止,或者是繼續運行到cancelation-point(取消點),由不同的cancelation狀態決定.線程收到CANCEL信號的缺省狀態是運行到取消點(pthread_create()創建線程的缺省狀態).
取消點包括以下一些函數:pthread_join(),pthread_testcancel(),pthread_condition_wait(),pthread_cond_timewait(),sem_wait(),sigwait()等等函數,以及read(),write()等會引起阻塞的系統調用都是取消點.而其他pthread函數都不會引起Cancelation動作。但是pthread_cancel的手 冊頁聲稱,由于LinuxThread庫與C庫結合得不好,因而目前C庫函數都不是Cancelation-point;但CANCEL信號會使線程從阻 塞的系統調用中退出,并置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統調用前后調用 pthread_testcancel(),從而達到POSIX標準所要求的目標,即如下代碼段:

pthread_testcancel();
retcode = read(fd,buffer,length);
pthread_testcancel();
  • 若線程處于無限循環,且沒有執行到取消點的必然路徑,則線程無法由外部的其他線程取消請求而終止,因此在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用.

  • 通過 int pthread_cancel(pthread_t tid);發送終止信號給thread線程,若成功則返回0,否則非0,成功發送并不意味著thread會終止.(那么檢查pthread_cancel的返回狀態不是沒有意義么???)

  • int pthread_setcancelstate(int state, int *oldstate):設置本線程對Cancel信號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)PTHREAD_CANCEL_DISABLE,分別表示收到信號后設為CANCLED狀態和忽略CANCEL信號繼續運行;old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。

  • int pthread_setcanceltype(int type, int *oldtype)設置本線程取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFEREDPTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態為Enable時有效,分別表示收到信號后繼續運行至下一個取消點再退出和 立即執行取消動作(退出);oldtype如果不為NULL則存入運來的取消動作類型值。

  • void pthread_testcancel(void)檢查本線程是否處于Canceld狀態,如果是,則進行取消動作,否則直接返回。


互斥鎖

pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

  • pthread_mutex_t 是posix下抽象出的一個鎖類型的結構:pthread_mutex_t.通過對該結構的訪問.顧名思義,加鎖以后,別人就無法打開,只有當鎖沒有關閉的時候才可以訪問資源.使用互斥鎖可以讓線程按順序執行.通常,互斥鎖通過確保一次只有一個線程執行代碼的臨界段來同步多個線程.互斥鎖還可以保護單線程代碼.要更改缺省的互斥鎖屬性,可以對屬性對象進行聲明和初始化。通常,互斥鎖屬性會設置在應用程序開頭的某個位置,以便可以快速查找和輕松修改。
  • pthread_mutex_init()函數是以動態方式創建互斥鎖的,參數attr指定了新建互斥鎖的屬性。如果參數attr為NULL,則使用默認的互斥鎖屬性,默認屬性為快速互斥鎖 。互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。函數成功完成之后會返回0,其他的任何值表示出現錯誤.執行成功后,互斥鎖被初始化為鎖住的狀態.

互斥鎖的屬性:

  1. PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,并在解鎖后按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。
  2. PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
  3. PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。
  4. PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。
鎖的一些操作
  1. int pthread_mutxe_lock(pthread_mutex_t *mutex)
  2. int phread_mutex_unlock(pthread_mutex_t *mutex)
  3. int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被占據時返回EBUSY而不是掛起等待。

死鎖

死鎖主要發生在有多個依賴鎖存在時, 會在一個線程試圖以與另一個線程相反順序鎖住互斥量時發生. 如何避免死鎖是使用互斥量應該格外注意的東西。總體來講, 有幾個不成文的基本原則:

  1. 對共享資源操作前一定要獲得鎖。
  2. 完成操作以后一定要釋放鎖。
  3. 盡量短時間地占用鎖。
  4. 如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。
  5. 線程錯誤返回時應該釋放它所獲得的鎖。

條件變量

#include <pthread.h>
pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *attr);
//成功返回0,其他的值表示錯誤

不能由多個線程同時初始化一個條件變量。當需要重新初始化或釋放一個條件變量時,應用程序必須保證這個條件變量未被使用。

int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
//返回0表示成功,其他值表示失敗

該函數將解鎖mutex參數指向的互斥鎖,并使當前線程阻塞在cv參數指向的條件變量上。被阻塞的線程可以被pthread_cond_signal函數,pthread_cond_broadcast函數喚醒,也可能在被信號中斷后被喚醒。pthread_cond_wait函數的返回并不意味著條件的值一定發生了變化,必須重新檢查條件的值。pthread_cond_wait函數返回時,相應的互斥鎖將被當前線程鎖定,即使是函數出錯返回。一般一個條件表達式都是在一個互斥鎖的保護下被檢查。當條件表達式未被滿足時,線程將仍然阻塞在這個條件變量上。當另一個線程改變了條件的值并向條件變量發出信號時,等待在這個條件變量上的一個線程或所有線程被喚醒,接著都試圖再次占有相應的互斥鎖。


pthread_cleanup_push()和pthread_cleanup_pop()的目的和作用

例如一個線程thread1:

pthread_mutex_lock(&mutex);
//一些會阻塞程序運行的調用,比如套接字的accept,等待客戶連接
sock = accept(......);            //這里是隨便找的一個可以阻塞的接口
pthread_mutex_unlock(&mutex);

上例中若線程1執行了accept(),線程會阻塞(也就是等在那里,有客戶端連接的時候才返回,或則出現其他故障),線程等待中......
這時候線程2發現線程1等了很久,不賴煩了,他想關掉線程1,于是調用pthread_cancel()或者類似函數,請求線程1立即退出。這時候線程1仍然在accept等待中,當它收到線程2的cancel信號后,就會從accept中退出,然后終止線程,注意這個時候線程1還沒有執行:pthread_mutex_unlock(&mutex);也就是說鎖資源沒有釋放,這回造成其他線程的死鎖問題。
所以必須在線程接收到cancel后用一種方法來保證異常退出(也就是線程沒達到終點)時可以做清理工作(主要是解鎖方面),pthread_cleanup_pushpthread_cleanup_pop就是用來做這樣的工作的。

pthread_cleanup_push(some_clean_func,...)
pthread_mutex_lock(&mutex);
//一些會阻塞程序運行的調用,比如套接字的accept,等待客戶連接
sock = accept(......);            //這里是隨便找的一個可以阻塞的接口
pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
return NULL;

上面的代碼,如果accept被cancel后線程退出,會自動調用some_clean_func函數,在這個函數中你可以釋放鎖資源。如果accept沒有被cancel,那么線程繼續執行,當pthread_mutex_unlock(&mutex);表示線程自己正確的釋放資源了,而執行pthread_cleanup_pop(0);也就是取消掉前面的some_clean_func函數。接著return線程就正確的結束了。
push進去的函數可能在以下三個時機執行:

  1. 顯示的調用pthread_exit();
  2. 在cancel點線程被cancel。
  3. pthread_cleanup_pop()的參數不為0時。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 轉自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay閱讀 1,628評論 0 52
  • 一、線程的創建和調度 1.線程是程序執行的某一條指令流的映像。 為了進一步減少處理機制的空轉時間,支持多處理器及減...
    穹藍奧義閱讀 1,124評論 2 5
  • 線程基礎 線程是進程的一個執行單元,執行一段程序片段,線程共享全局變量;線程的查看可以使用命令或者文件來進行查看;...
    秋風弄影閱讀 749評論 0 0
  • iOS 多線程系列 -- 基礎概述iOS 多線程系列 -- pthreadiOS 多線程系列 -- NSThrea...
    shannoon閱讀 2,669評論 1 8
  • 線程 在linux內核那一部分我們知道,線程其實就是一種特殊的進程,只是他們共享進程的文件和內存等資源,無論如何對...
    大雄good閱讀 676評論 0 2