線程控制

注:函數未經說明所有定義在<pthread.h>程序員

1.分離狀態編程

在任何一個時間點上,線程是可結合的(joinable),或 者是分離的(detached)。一個可結合的線程可以被其餘線程收回其資源和殺死;在被其餘線程回收以前,它的存儲器資源(如棧)是不釋放的。相反,一 個分離的線程是不能被其餘線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。數組

   線程的分離狀態決定一個線程以什麼樣的方式來終止本身。在默認狀況下線程是非分離狀態的,這種狀況下,原有的線程等待建立的線程結束。只有當 pthread_join()函數返回時,建立的線程纔算終止,才能釋放本身佔用的系統資源。而分離線程不是這樣子的,它沒有被其餘的線程所等待,本身運 行結束了,線程也就終止了,立刻釋放系統資源。程序員應該根據本身的須要,選擇適當的分離狀態。因此若是咱們在建立線程時就知道不須要了解線程的終止狀 態,則能夠pthread_attr_t結構中的detachstate線程屬性,讓線程以分離狀態啓動。安全

        設置線程分離狀態的函數爲 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數可選爲PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這裏要注意的一點是,若是設置一個線程爲分離線程,而這個線程運行又很是快,它極可能在 pthread_create函數返回以前就終止了,它終止之後就可能將線程號和系統資源移交給其餘的線程使用(有pthread_join的不會這樣,由於他要進行線程的資源回收),這樣調用pthread_create的 線程就獲得了錯誤的線程號。要避免這種狀況能夠採起必定的同步措施,最簡單的方法之一是能夠在被建立的線程裏調用 pthread_cond_timewait函數,讓這個線程等待一下子,留出足夠的時間讓函數pthread_create返回。設置一段等待時間,是 在多線程編程裏經常使用的方法。可是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。數據結構

2線程屬性多線程

定義:每一個對象都有一個對應的屬性對象,好比說線程對應這線程屬性對象,互斥量對應着互斥量屬性對象併發

在每個屬性對象中,咱們是不知道屬性對象中的結構的,這樣有利於加強數據的可移植性app

2.1線程屬性初始化銷燬異步

(1)pthread_attr_init(pthread_attr_t* attr);函數

對一個線程屬性進行初始化,初始化後的屬性是系統默認屬性

(2)pthread_attr_destroy(pthread_attr_t* attr);

對一個分配了的屬性進行銷燬

2.2線程屬性如何設置

detachstate 線程的分離狀態屬性;guardsize 線程棧末尾的警惕緩衝區大小(字節數);stackaddr 線程棧的最低地址;stacksize 線程棧的最小長度

2.2.1關於分離屬性的設置以及產看

PTHREAD_CREATE_DETACKED 分離方式釋放

PTHREAD_CREATE_JOINABLE 正常方式啓動線程

(1)int pthread_attr_getdetackstate(const pthread_attr_t *restrict attr,int *detackstate);

得到啓動方式

(2)int pthread_attr_setdetackstate(const pthread_attr_t *restrict attr,int *detachstate);

設置啓動方式,是以什麼方式進行啓動的

2.2.2線程棧屬性

對線程的棧屬性進行設置的時候,應該首先對這兩個屬性進行檢查,看一下操做系統是否支持此屬性

用sysconf函數進行驗證 _SC_THREAD_ATTR_STACKADDR,_SC_THREAD_ATTR_STACKSIZE

(1)int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize);

首先咱們要進行明確,線程棧是使用的進程棧,每一個線程棧在默認的時候分配的是相同大小,可是如此分配會出現,分配不均勻以及其餘的一些問題,因此應該進行設置

attr指的是線程屬性

stackaddr指的是能夠用做線程棧地址的最低可尋址地址,可是折不必定就是一個棧的開始爲止,要看棧是向上仍是向下增加的

stacksize指的是棧的大小

記住,stackaddr和stacksize所肯定的範圍不能與原來分配的地址範圍進行重疊,注意分配空間應該用malloc進行分配

(2)int pthread_attr_getstack(const pthread_attr_t *restrict attr,void **restrict stackaddr,size_t *restrict stacksize);

獲得棧的地址,獲得棧的大小

(3)int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,size_t *restrict stacksize);

獲得棧的大小

(4)int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize)

設置棧的大小

設置棧的大小不能小於STACK_MIN

2.2.3棧溢出擴展內存大小

這個屬性默認值是由系統決定的,經常使用值是系統的頁大小

當對棧的屬性即2.2.2進行了操做的時候,進行了設置,這個屬性值就沒有用了,由於咱們本身進行棧的管理

(1)int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict guardsize);

對棧的緩衝去進行觀察

(2)int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);

對緩衝區進行設置

3同步屬性

定義:同步就是指一個進程在執行某個請求的時候,若該請求須要一段時間才能返回信息,那麼這個進程將會一直等待下去,直到收到返回信息才繼續執行下去;異步是指進程不須要一直等下去,而是繼續執行下面的操做,無論其餘進程的狀態。當有消息返回時系統會通知進程進行處理,這樣能夠提升執行的效率。
咱們平時常常討論的同步問題多發生在多線程環境中的數據共享問題。即當多個線程須要訪問同一個資源時,它們須要以某種順序來確保該資源在某一特定時刻只能
被一個線程所訪問,若是使用異步,程序的運行結果將不可預料。所以,在這種狀況下,就必須對數據進行同步,即限制只能有一個進程訪問資源,其餘線程必須等待。
實現同步的機制主要有臨界區、互斥、信號量和事件

4互斥量屬性

4.1互斥量屬性進行初始化

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
用來對互斥量屬性進行初始化,初始化後的屬性是默認的
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
用來對互斥量屬性進行銷燬
列如::::
pthread_mutexattr_t arg;
pthread_mutexattr_init(&arg);

4.2進程共享屬性
進程共享屬性是可選的,有的系統是不支持的,須要使用sysconf進行判斷,_SC_THREAD_PROCESS_SHARED進行判斷是否支持共享屬性的支持
什麼叫作共享屬性?注意,當多個線程能夠訪問同一個同步對象,這裏規定的是在進程中實現的,在不一樣的進程中是不能實現的,要是想要在不一樣的進程中實現,須要進行設置PTHREAD_PROCESS_SHARED,若是隻是在同一個進程中的多個線程中共享的話,只是設置爲PTHREAD_PROCESS_PRIVATE;若是設置成共享的話,就能夠用來同步進程!!!
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
這個函數是用來設置的共享屬性的
attr:表明互斥量屬性
pshared:PTHREAD_PROCESS_SHARED(進程共享屬性),PTHREAD_PROCESS_PRIVATE(進程私有屬性)在這種狀況下,pthread線程庫提供有效的互斥量實現,這是多線程的默認互斥量進程共享屬性
int pthread_mutexattr_getshared(const pthread_mutexattr_t *restrict attr,int *restrict pshared);
用來獲得互斥量屬性中的進程共享屬性設置
4.3互斥量健壯屬性
健壯屬性的前提條件下,是必須首先設置進程共享屬性爲PTHREAD_PROCESS_SHARED
爲何須要健壯屬性,由於互斥量在多個進程中共享,當在一個進程中佔有某一個互斥量的時候,這是這個進程死掉了,此時運行B進程,B進程會由於被拖住(這是由於健壯屬性被設置爲PTHREAD_MUTEX_STALLED,狀況至關與死鎖),當健壯屬性被設置成PTHREAD_MUTEX_ROBUST的時候,此時若是在發生上訴狀況,進程B就不會被拖住,應該進行繼續運行,可是,pthread_mutex_lock()返回的是EOWNERDEAD,也是進程繼續運行
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,int robust);
設置健壯屬性
attr:互斥量屬性對象
robust:PTHREAD_MUTEX_ROBUST(健壯屬性),PTHREAD_MUTEX_STALLED(非健壯屬性)
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr,int *restrict robust)
獲得互斥量屬性關於健壯性的取值
int pthread_mutex_consistent(pthread_mutex_t *mutex);
若是持有 mutex 的進程退出,另一個線程在 pthread_mutex_lock 的時候會返回 EOWNERDEAD。這時候你須要調用 pthread_mutex_consistent 函數來清除這種狀態,不然後果自負。

 

寫成代碼就是這樣子:

 

int r = pthread_mutex_lock(lock); if (r == EOWNERDEAD) pthread_mutex_consistent(lock);
4.4類型互斥量屬性
此屬性控制着互斥量的鎖定狀態
PTHREAD_MUTEX_NORMAL:一種標準互斥量的類型,不作任何特殊的錯誤檢查或死鎖檢測
PTHREAD_MUTEX_ERRORCHECK:提供錯誤檢查
PTHREAD_MUTEX_RECURSIVE:同一線程中的遞歸鎖,必須在同一線程中才能發生遞歸,若是在不一樣的線程裏面,和本來的互斥量是同樣的,在同一線程中對同一互斥量進行加鎖不會致使死鎖的發生!!!
PTHREAD_MUTEX_DEFAULT:默認
互斥量類型                                            沒有解鎖時從新加鎖           不佔用時解鎖            在已解鎖時解鎖
PTHREAD_MUTEX_NORMAL                         死鎖                               未定義                   未定義
PTHREAD_MUTEX_ERRORCHECK              返回錯誤                        返回錯誤               返回錯誤
PTHREAD_MUTEX_RECURSIVE                   容許                                返回錯誤               返回錯誤
PTHREAD_MUTEX_DEFAULT                       未定義                             未定義                   未定義

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);

用來獲得互斥量的類型互斥量屬性

int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);

type是上方的幾個值

在這裏面要特別注意遞歸變量,在同一個線程中,不要在條件變量的互斥量使用遞歸!!!好比說若是在使用互斥量(此時遞歸屬性)在使用條件變量的以前(屢次進行鎖的佔有),好比所互斥量已經鎖定,此時條件變量不能接受條件的變換,致使死鎖!!!!

5讀寫鎖屬性

5.1初始化

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

對讀寫鎖屬性變量進行初始化

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

銷燬讀寫鎖屬性變量

5.2進程共享屬性

進程貢獻屬性與互斥量的進程共享屬性是一致的

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr,int *restrict pshared);

取得讀寫鎖屬性的進程共享屬性的取值

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int pshared);

設置讀寫鎖屬性的進程共享屬性的取值

6條件變量屬性

6.1初始化條件變量屬性

int pthread_condattr_init(pthread_condattr_t *attr);

進行條件變量屬性的初始化

int pthread_condattr_destroy(pthread_condattr_t *attr);

進行條件變量屬性的銷燬

6.2進程共享屬性

int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,int *restrict pshared);

和互斥量進程共享屬性一致

int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);

和互斥量進程共享屬性一直

6.3時鐘屬性

int pthread_condattr_setclock(pthread_condattr_t *attr,clockid_t clock_id);

進行時鐘id的選擇,這些時鐘來源於6-8,這些時鐘用於pthread_cond_timewait()等待的時鐘

int pthread_condattr_getclock(pthread_condattr_t *attr,clockid_t clock_id);

獲得屬性的時鐘id

7屏障屬性

7.1對屏障屬性進行初始化和銷燬

int pthread_barrierattr_init(pthread_barrierattr_t *attr);

attr:屏障屬性對象

int pthread_barrierattr_detroy(pthread_barrierattr_t *attr);

attr:將要銷燬的屏障屬性對象

7.2屏障進程共享屬性

int pthread_barrierattr_getshared(const pthread_barrierattr_t *restrict attr,int *restrict pshared);

attr:屏障屬性對象

pshared:看是否支持屏障屬性對象屬性

int pthread_barrierattr_setshared(pthread_barrierattr_t *attr,int *pshared);

pshared:PTHREAD_PROCESS_SHARED(進程共享屬性),PTHREAD_PROCESS_PRIVATE(進程私有屬性)在這種狀況下,pthread線程庫提供有效的互斥量實現,這是多線程的默認互斥量進程共享屬性

8重入

8.1什麼是線程安全以及哪些狀況形成線程不安全

8.1.1線程安全:一個函數被稱爲線程安全的(thread-safe),當且僅當被多個併發進程反覆調用時,它會一直產生正確的結果。若是一個函數不是線程安全的,咱們就說它是線程不安全的(thread-unsafe)。咱們定義四類(有相交的)線程不安全函數。

8.1.2四類不安全的線程函數

(1)不保護共享變量的函數

 

將這類線程不安全函數變爲線程安全的,相對比較容易:利用像P和V操做這樣的同步操做來保護共享變量。這個方法的優勢是在調用程序中不須要作任何修改,缺點是同步操做將減慢程序的執行時間。

(2)保持跨越多個調用的狀態函數

 

一個僞隨機數生成器是這類不安全函數的簡單例子。

 

 

 

unsigned int next = 1; 
int rand(void)
{
     next = next * 1103515245 + 12345;
     return (unsigned int) (next / 65536) % 32768;
}
 
void srand(unsigned int seed)
{
      next = seed;
}

 

 

 

rand函數是線程不安全的,由於當前調用的結果依賴於前次調用的中間結果。當咱們調用srand爲rand設置了一個種子後,咱們反覆從一個單線 程中調用rand,咱們可以預期一個可重複的隨機數字序列。可是,若是有多個線程同時調用rand函數,這樣的假設就不成立了。

 

使得rand函數變爲線程安全的惟一方式是重寫它,使得它再也不使用任何靜態數據,取而代之地依靠調用者在參數中傳遞狀態信息。這樣的缺點是,程序員如今要被迫改變調用程序的代碼。

 

(3)返回指向靜態變量指針的函數

 

某些函數(如gethostbyname)將計算結果放在靜態結構中,並返回一個指向這個結構的指針。若是咱們從併發線程中調用這些函數,那麼將可能發生災難,由於正在被一個線程使用的結果會被另外一個線程悄悄地覆蓋了。

 

有兩種方法來處理這類線程不安全函數。一種是選擇重寫函數,使得調用者傳遞存放結果的結構地址。這就消除了全部共享數據,可是它要求程序員還要改寫調用者的代碼。

 

若是線程不安全函數是難以修改或不可修改的(例如,它是從一個庫中連接過來的),那麼另一種選擇就是使用lock-and-copy(加鎖-拷 貝)技術。這個概念將線程不安全函數與互斥鎖聯繫起來。在每一個調用位置,對互斥鎖加鎖,調用函數不安全函數,動態地爲結果非配存儲器,拷貝函數返回的結果 到這個存儲器位置,而後對互斥鎖解鎖。一個吸引人的變化是定義了一個線程安全的封裝(wrapper)函數,它執行lock-and-copy,而後調用 這個封轉函數來取代全部線程不安全的函數。例以下面的gethostbyname的線程安全函數。

 

 

 

struct hostent* gethostbyname_ts(char* host)
{
    struct hostent* shared, * unsharedp;
    unsharedp = Malloc(sizeof(struct hostent));
    P(&mutex)
    shared = gethostbyname(hostname);
    *unsharedp = * shared;
    V(&mutex);
    return unsharedp;
}

 

 

 

(4)調用線程不安全函數的函數

 

若是函數f調用線程不安全函數g,那麼f就是線程不安全的嗎?不必定。若是g是類2類函數,即依賴於跨越屢次調用的狀態,那麼f也是不安全的,並且 除了重寫g之外,沒有什麼辦法。然而若是g是第1類或者第3類函數,那麼只要用互斥鎖保護調用位置和任何獲得的共享數據,f可能仍然是線程安全的。好比上 面的gethostbyname_ts。

8.2POSIX中不能保證線程安全的函數

 

上圖都是不能保證重入成功的函數針對次,POSIX有了替代的線程安全函數

圖12-10是代替的線程可重入函數

POSIX還提供了一線程安全的方式管理FILE對象的方法

#include <stdio.h>

void flockfile(FILE *fp);

對於FILE對象獲取關聯的鎖,就是佔用鎖,這個鎖是遞歸的,此線程在佔用了這把鎖的時候能夠繼續佔用此把鎖,不會發生阻塞致使死鎖。注意這種鎖的實現並無要求,可是咱們平時使用的操做FILE對象的函數都使用了次類型鎖,可是沒有強求必須使用flockfile,可是做用是同樣的(這個函數基本在應用中不適用

int ftrylockfile(FILE *fp);

嘗試得到鎖,若是失敗,則返回非0值,若成功,則返回0

void funlockfile(FILE *fp);

對鎖進行解鎖

注意!!這樣在每個操做
FILE對象的函數調用(庫提供)這樣使用鎖會使效率下降,所以咱們把鎖的控制權放給用戶會提升效率

,可是要注意使用鎖的範圍

#include <stdio.h>

int getchar_unlocked(void);

不加鎖版本,若成功返回下一個字符,若是文件尾或者出錯,返回EOF

int getc_unlocked(FILE *fp);

不加鎖版本,若成功返回下一個字符,若是文件尾或者出錯,返回EOF

 

int putchar_unlocked(int c);

不加鎖版本,輸出一個字符,向標準流中,若成功,返回c,失敗返回EOF

int putc_unlocked(int c,FILE *fp);

不加鎖版本,輸出一個字符,向標準流中,若成功,返回c,失敗返回EOF

這幾個函數,必須在flockfile和funlockfile中的調用中包圍,不然儘可能不要使用這幾個函數

9線程私有數據

9.1 概念及做用 
在單線程程序中,咱們常常要用到"全局變量"以實現多個函數間共享數據。在多線程環境下,因爲數據空間是共享的,所以全局變量也爲全部線程所共有。如今有一全局變量,全部線程均可以使用它,改變它的值。而若是每一個線程但願能單獨擁有它,那麼就須要使用線程存儲了。表面上看起來這是一個全局變量,全部線程都 可使用它,而它的值在每個線程中又是單獨存儲的。這就是線程存儲的意義。這樣的數據結構能夠由Posix線程庫維護,稱爲線程私有數據 (Thread-specific Data,或TSD)。
具體用法以下:

 

(1)建立一個類型爲pthread_key_t類型的變量。

 

(2)調用pthread_key_create()來建立該變量。該函數有兩個參數,第一個參數就是上面聲明的pthread_key_t變量,第二個參數是一個清理函數,用來在線程釋放該線程存儲的時候被調用。該函數指針能夠設成NULL,這樣系統將調用默認的清理函數。

 

(3)當線程中須要存儲特殊值的時候,能夠調用pthread_setspcific()。該函數有兩個參數,第一個爲前面聲明的pthread_key_t變量,第二個爲void*變量,這樣你能夠存儲任何類型的值。

 

(4)若是須要取出所存儲的值,調用pthread_getspecific()。該函數的參數爲前面提到的pthread_key_t變量,該函數返回void *類型的值。

9.2 建立和註銷 
int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));
該函數從TSD池(裏面存儲了不少key的值)中分配一項,將其值賦給key供之後訪問使用。若是destr_function不爲空,在線程退出(pthread_exit())時將以key所關聯的數據爲參數調用destr_function(),以釋放分配的緩衝區。
不論哪一個線程調用pthread_key_create(),所建立的key都是全部線程可訪問的但各個線程可根據本身的須要往key中填入不一樣的值,這就至關於提供了一個同名而不一樣值的全局變量。在LinuxThreads的實現中,TSD池用一個結構數組表示:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };
建立一個TSD就至關於將結構數組中的某一項設置爲"in_use",並將其索引返回給*key,而後設置destructor函數爲destr_function。
註銷一個TSD採用以下API: 

int pthread_key_delete(pthread_key_t key);
這個函數並不檢查當前是否有線程正使用該TSD,也不會調用清理函數(destr_function),而只是將TSD釋放以供下一次調用 pthread_key_create()使用。在LinuxThreads中,它還會將與之相關的線程數據項設爲NULL(見"訪問")。
9.3訪問 

int pthread_setspecific(pthread_key_t key, const void *pointer); 

寫入(pthread_setspecific())時,將pointer的值(不是所指的內容)與key相關聯,而相應的讀出函數則將與key相關聯的數據讀出來。數據類型都設爲void *,所以能夠指向任何類型的數據。

void * pthread_getspecific(pthread_key_t key) ;
獲得訪問線程的key(指定的)中數據

10取消選項

10.1取消

其實取消選項是線程的兩個屬性,可是並無包含在pthread_attr_t結構中,這兩個屬性是可取消狀態可取消類型,這兩個屬性注意影響的是調用pthread_cancel 的行爲

10.2可取消狀態

int pthread_setcancelstate(int state,int *oldstate);

state:可取消狀態,第一種是PTHREAD_CANCEL_DISABLE禁止可取消狀態,當選用次狀態的時候,在另外一個線程的取消檢測點,檢測到取消,可是並不取消線程,取消請求處於掛起狀態;第二種是PTHREAD_CANCEL_ENABLE這個時候調用pthread_cancel的時候在檢測點殺死線程

oldstate:保存的是上一次取消狀態屬性

注意,當一個進程被設置爲PTHREAD_CANCEL_DISABLE時候,這個時候在把他設置成PTHREAD_CANCEL_ENABLE,線程這個時候將在下一個檢測點的時候將線程進行取消

void pthread_testcancel(void);

設置一個檢測點,若是在一個具體的函數調用中長時間運行,則須要設置這個檢測點

檢測點的起效!!某個取消請求正處於掛起狀態,而且取消並無設爲無效,那麼就會線程被取消

若是取消被設置爲無效,則沒有用這個檢測點

10.3設置取消點檢測的函數

 

 

10.4可取消類型

int pthread_setcanceltype(int type, int *oldtype)

注意:咱們默認的可取消類型是推遲取消,調用pthread_cancel()之後,並不會出現真正的取消,相反要等到一個檢測點的時候,

type:PTHREADCANCEL_DEFERRED延遲取消,PTHREAD_CANCEL_ASYNCHRONOUS當即取消

oldtype:這個是將原來的取消方式存入oldtype中

11線程和信號

每一個線程都有本身的信號屏蔽字,可是每一個線程若是修改線程屏蔽字,則全部的線程共享此線程屏蔽字,進程中的信號是遞送到單個線程中的,若是一個信號是與硬件故障有關,則這個信號通常被送到引發這個故障的線程中

11.1線程信號屏蔽字

#include <signal.h>

int pthread_sigmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);

how參數取值

SIG_BLOCK:把信號添加到線程信號屏蔽字中

SIG_SETMASK:用信號集替換信線程的信號屏蔽字

SIG_UNBLOCK:從信號屏蔽字移除信號集中

set:信號集

oset:原來的信號集

int sigwait(const sigset_t *restrict set,int *restrict signop);

功能:若是某個信號在調用sigwait的時候是掛起狀態,則sigwait將無阻塞返回,在返回以前,sigwait將從進程中刪除那些處於掛起等待狀態的信號,若是支持排隊,而且多個信號實例被掛起,sigwait將會移除該信號的一個實例,其餘的實例還要繼續排隊

爲了不錯誤的發生,在sigwait以前,每個線程都應該阻塞那些他正在等待的信號(實則是將這些阻塞的信號送往一個叫作信號處理的線程),sigwait函數被調用以後立刻原子性的取消阻塞狀態,直到有一個新的信號被遞送以後,恢復阻塞狀態,而且sigwait在返回以前,恢復信號屏蔽字

經過以上方式,sigwait其實作的就是把異步信號處理方式,改變成了同步信號處理方式

set:線程等待的信號集

signorp:當調用一次sigwait的時候,而且有set指定的信號阻塞(兩個以上)signorp指的是最近被遞送的那一個信號

int pthread_kill(pthread_t thread,int signo);

thread:線程的ID

signo:信號的名字

12線程與fork

12.1相關概念關係以及存在問題

 

         fork函數調用會建立子進程,子進程的地址空間是在調用fork時父進程地址空間的拷貝。由於子進程地址空間跟父進程同樣,因此調用fork時,子進程繼承了父進程中的全部互斥鎖、讀寫鎖和條件變量(包括它們的狀態)。

        但在多線程環境中,調用fork時,子進程中只有一個線程存在,這個線程是調用fork函數的那個線程,其餘線程都沒有被拷貝。

 

        根據上述兩點,子進程中的鎖可能被不存在的線程所擁有,這樣子進程將無法獲取或釋放這些鎖。針對這個問題有一個解決辦法,即在調用fork以前,線程先獲 取進程中全部鎖,在調用fork後分別在父子進程中釋放這些鎖,從而能夠從新利用這些資源。由於fork以前,當前線程擁有全部的鎖,因此fork以後, 當前線程繼續存在,子進程能夠安全的釋放這些鎖。

 

    固然,在調用fork後,子進程立刻調用exec(在exec和fork以後的只能調用異步信號安全函數),就無需考慮這些問題了,由於子進程地址空間被徹底更換了。

12.2解決上訴問題函數

函數pthread_atfork專門用來解決這種問題:

int pthread_atfork ( void (*prepare)(void), void (*parent)(void), void (*child)(void) );

 

pthread_atfork安裝一些在fork調用時的回調函數。

prepare函數將在fork建立子進程以前被調用,一般能夠用來獲取進程中的全部 鎖;

parent在fork建立子進程後返回前在父進程中被調用,能夠用來釋放父進程中的鎖;

child在fork建立子進程後fork返回前在子進程中 被調用,能夠用來釋放子進程中的鎖。

給這三個參數傳遞NULL,表示不調用該函數。

 

能夠調用pthread_atfork屢次註冊多組回調函數,這時,回調函數調用的順序規定以下:

 

①prepare函數調用順序與它們的註冊順序相反;

 

②parent和child函數的調用順序與註冊順序相同。

 



相關文章
相關標籤/搜索