Unix環境高級編程-11-線程

進程,線程模型


線程對象

  • 線程對象擁有的域以下:多線程

  • 線程標識,用於在一個進程中區分多個線程,因此線程標識只要求在一個進程內是惟一的便可.函數

    • pthread 中使用 pthread_t 來保存線程標識,測試

    • pthread_self(),獲取當前線程的線程標識.spa

    • pthread_equal(tid1,tid2),用來比較2個線程標識是否相同.
      線程

  • 棧,每一個線程都有本身的棧,見上.code

  • 屏蔽信號集,每一個線程都有本身的屏蔽信號集,此時進程對象中的屏蔽信號集被忽略.對象

  • 未決信號集,每一個線程都有本身的未決信號集,此時進程對象中的未決信號集仍然有效,參見 sigpending().繼承

線程建立

  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);用於建立一個線程,其中新線程的線程標識將存入 thread 指向的緩衝區中.各參數:遞歸

    • attr 用於指定新線程的線程屬性,若 attr 爲0,則表示新線程取默認屬性.接口

    • start_routine,arg 新線程的入口函數,正如 main() 函數爲主線程的入口函數同樣.

  • 新線程的線程對象的屬性大多數都是直接繼承自調用線程(即調用 pthread_create() 的線程),除了:

    • 棧,

    • 未決信號集,在新線程中,未決信號集被清空.

線程屬性

  • pthread_attr_t 對象中存放了線程屬性的值,在建立線程時能夠傳遞一個 pthread_attr_t 對象來爲線程對象指定屬性值.接口:

    • int pthread_attr_init(pthread_attr_t *attr);將 attr 中線程屬性的值初始化爲實現支持的默認值.

    • int pthread_attr_destroy(pthread_attr_t *attr);該接口主要進行2件事:

      1. 若在 pthread_attr_init() 中分配了資源,則會在此時釋放資源;

      2. 將 attr 標記爲不可用,防止 attr 再一次用於 pthread_create().

線程具備如下屬性

  • detachstate,線程分離狀態,參見'線程的分離狀態';若爲 PTHREAD_CREATE_DETACHED,則代表線程處於分離狀態;若爲 PTHREAD_CREATE_JOIN;接口:

    • int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *state);獲取 detachstate 屬性的值,並將其存入 *state 中.

    • int pthread_attr_setdetachstate(pthread_attr_t *attr,int state);將 state 設置爲 detachstate 屬性的值.

線程終止

線程終止的狀況

  • 在線程執行的任一時刻調用 pthread_exit(),都會使線程終止.

    • void pthread_exit(void *retval);終止當前線程,retval 自己將設置爲線程的退出狀態;

  • 從線程的入口函數 start_routine() 中返回.

  • 被其餘線程取消.

    • int pthread_cancel(pthread_t thread);向線程 thread 發送取消請求,而後返回,並不等待線程被取消.

線程清理處理程序

  • 線程清理處理程序,在線程終止時調用;以棧的形式保存.

    • void pthread_cleanup_push(void (*routine)(void *),void *arg);安裝線程清理處理程序 routine(arg),即將 routine() 放入棧頂.

    • void pthread_cleanup_pop(int execute);移除位於棧頂的線程清理處理程序,若 execute 不爲0,則執行該線程清理處理程序,不然不執行.

    • pthread_cleanup_push()/pthread_cleanup_pop() 因爲實現,通常要求成對出現.

線程的分離狀態

  • 當線程終止時,

    • 若線程處於分離狀態,則線程的底層資源會被直接釋放,

    • 不然,會保存線程的底層資源直至其餘線程調用了 pthread_join() 回收該線程

  • int pthread_detach(pthread_t thread);將線程 thread 設置爲分離狀態.

收到取消請求後

  1. 首先根據 cancelstate 的值來判斷是否阻塞取消請求;

    1. 若 cancelstate 爲 PTHREAD_CANCEL_DISABLE,則會阻塞取消請求直至 cancelstate 從新設置爲 PTHREAD_CANCEL_ENABLE,此時纔會將取消請求遞送給線程;

    2. 若 cancelstate 爲 PTHREAD_CANCEL_ENABLE,則會馬上遞送取消請求.

  2. 當取消請求遞送到線程以後,再根據 canceltype 的值來處理這個取消請求,

    1. 若 canceltype 爲 PTHREAD_CANCEL_DEFERRED,則此時並不會馬上終止當前線程,而是設置一個標誌(設爲 is_cancel 標誌),直至取消點時纔會取消線程.

    2. 若 canceltype 爲 PTHREAD_CANCEL_ASYNCHRONOUS,則此時會馬上終止當前線程,

  • pthread_setcancelstate()/pthread_setcanceltype() 用於設置/獲取 cancelstate,或者 canceltype 的值.

  • int pthread_testcancel(),該函數會測試是否設置了取消標誌,若設置,則馬上終止當前線程,該函數的實現大體以下:

    int pthread_testcancel(){
        if(is_cancel) // 此時代表線程已經收到取消請求.
            return pthread_exit(PTHREAD_CANCELED);// 調用 pthread_exit() 來終止當前線程
        return 0;
    }
  • 取消點,就是一個函數;全部可能會阻塞當期線程的函數(如 read(),pthread_cond_wait())都是取消點;這樣當線程所以而阻塞時,接受會取消請求,就會即便被取消;這些函數的實現我猜想多是:

void func(){
    while(true){
        pthread_testcancel();
        if(條件知足)
            return;
    }
}

線程私有數據

  • 線程私有數據,是一個內存塊,線程在訪問其私有數據時不須要擔憂與其餘線程的同步訪問問題,便可以認爲線程私有數據只能被擁有者線程訪問.

  • pthread_key_t,鍵,當一個鍵建立以後,能夠被全部線程使用,即每個線程均可以將私有數據的地址存放在該鍵中,接口:

    • int pthread_key_create(pthread_key_t *key,void (*destructor)(void*));建立鍵 key,其中:

      1. destructor 指定了鍵的析構函數,若爲0,則說明鍵不須要析構函數.

    • int pthread_setspecific(pthread_key_t key,const void *private_buf);線程調用該函數將其私有數據地址保存在鍵 key 中.

    • void* pthread_getspecific(pthread_key_t key);線程調用該函數獲取其保存在鍵 key 中的私有數據地址;若線程從未在 key 中保存過私有數據地址(即從未調用 pthread_setspecific()),則此時返回0.

    • int pthread_delete(pthread_key_t *key);線程調用該函數刪除其在 key 中保存的私有數據地址(我認爲至關於調用 pthread_setspecific(key,0));此時不會調用鍵 key 的析構函數.

  • 鍵的析構函數,當線程終止時,會遍歷當前存在的全部鍵,對於每個鍵,若線程在其中保存的私有數據地址不爲0,則以私有數據地址爲參數調用鍵的析構函數;整個過程會重複若干次直至線程在當前存在的全部鍵中保存的私有數據地址均爲0.

線程與信號

  • 與信號有關的屬性,信號處理方式,屏蔽信號集,未決信號集;在多線程環境中:

    • 信號處理方式,只存在於進程對象中,爲全部線程共享

    • 屏蔽信號集,存在於每個線程對象中,並不存在於進程對象中

    • 未決信號集,存在於每個線程對象與進程對象中,

  • 通常狀況下,信號都是發送至進程對象;此時進程對象會選擇第一個未屏蔽該信號的線程來處理該信號(即若對該信號的處理方式爲捕捉,則在此線程來運行信號處理函數;若信號的處理方式爲忽略,則忽略信號;若處理方式爲默認,則忽略或者終止進程或者暫停進程).若全部線程均屏蔽了信號,則將信號加入到進程對象的未決信號集中.

    • 實際測試發現,進程對象總會選擇 main() 函數所在線程來處理信號;若 main 線程屏蔽了該信號,則將信號加入到進程的未決信號集中.

  • 在如下幾種狀況下,信號是直接遞送給線程,此時若線程屏蔽了該信號,則將信號加入到線程的未決信號集中;不然在該線程中處理信號.

    • 由線程中某條指令執行後產生的信號,如:SIGBUS,SIGFPE,...

    • 當線程對一個斷開連接的套接字寫入數據時,產生的 SIGPIPE 信號

    • 當前進程中其餘線程調用 pthread_kill()時

若干個接口

  • int pthread_sigmask(int how,const sigset_t *set,sigset_t *oldset);等同於 sigprocmask(),只是 pthread_sigmask() 改變的是調用線程的屏蔽信號集.

  • int pthread_kill(pthread_t thread,int signo);將信號 signo 發送給線程 thread.

  • int sigwaitinfo(const sigset_t *set, siginfo_t *info);掛起當前線程直至 set 中有一個信號處於未決狀態,此時爲該信號生成 siginfo_t 信息,並存入 info 指向的緩衝區中.該函數的行爲以下:

while(true){
    if(set 中有一個信號存在與線程,或者進程的未決信號集中)
        從未決信號集中移除該信號;
        爲該信號生成 siginfo_t 信號,並存入 info 指向的緩衝區中;
        return 信號值;
    else
        掛起當前線程一段時間;// 即主動放棄 CPU;
}

線程與 fork()

  • 當父進程中存在多個線程時,此時調用 fork() 建立的子進程中只有一個線程,即父進程中調用 fork() 的線程.同時因爲子進程徹底複製父進程的內存空間,全部子進程同時繼承了父進程中全部互斥量,讀寫鎖,條件變量的狀態,以下:

  • 假設在父進程中存在兩個互斥量 mutex1,mutex2;其中 mutex1 被線程B擁有,mutex2被線程C擁有鎖;在線程A調用 fork() 建立子進程以後;因爲子進程徹底繼承父進程的地址空間,因此在子進程中仍存在 mutex1,mutex2;可是mutex1,mutex2的擁有線程卻在子進程中不存在,因此子進程沒法釋放,而且銷燬 mutex1,mutex2;

  • int pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));創建 fork() 處理程序.

    • prepare,在建立子進程以前,在父進程中調用,而且調用順序與安裝順序相反.

    • parent,在建立子進程以後,在父進程中調用,而且調用順序與安裝順序相同.

    • child,在建立子進程以後,在子進程中調用,而且調用順序與安裝順序相同.

線程同步

鎖的模型

  • 多個進程同時讀寫一塊內存區域,可能會形成衝突問題,解決方法是爲該內存區域加上一把鎖,任何線程在讀寫這塊內存區域以前必須得到鎖;在任一時刻,鎖只能被一個線程擁有.這樣就能夠避免了衝突.

互斥量

  • 互斥量,就是那把保護內存區域的"鎖";此時鎖有2個狀態:加鎖,未加鎖.任何線程在讀寫內存區域以前都必須加鎖;

接口

  • pthread_mutex_t,數據類型,用來保存一個互斥量對象.

  • int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutex_attr);

  • int pthread_mutex_destroy(pthread_mutex_t *mutex);釋放在 init() 期間分配的資源,將 mutex 設置爲無效.

  • int pthread_mutex_lock(pthread_mutex_t *mutex);對 mutex 進行加鎖,若 mutex 已經加鎖,則阻塞直至其上的鎖被釋放.

  • int pthread_mutex_unlock(pthread_mutex_t *mutex);釋放 mutex 上的鎖,此時 mutex 變爲未加鎖狀態.

  • int pthread_mutex_trylock(pthread_mutex_t *mutex);對 mutex 進行加鎖,若 mutex 已經加鎖,則出錯返回,而不會阻塞.

屬性

  • pthread_mutexattr_t,互斥量的屬性存放在 pthread_mutexattr_t 對象中,能夠在調用 pthread_mutex_init() 時傳遞一個 pthread_mutexattr_t 對象來初始化新建互斥量 mutex 的值.接口:

    • int pthread_mutexattr_init(pthread_mutexattr_t *attr);將 attr 保存的互斥量屬性初始化爲默認值.

    • int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);該函數主要作2件事,以下:

      1. 釋放在 pthread_mutexattr_init() 時分配的資源;

      2. 將 attr 標記爲不可用,防止其再次用於 pthread_mutex_init().

互斥量具備如下屬性:

  • pshared,進程間共享,若 pshared 屬性的值爲 PTHREAD_PROCESS_PRIVATE,則此時互斥量不是進程間共享,即不能用於同步多個進程.若 pshared 屬性的值爲 PTHREAD_PROCESS_SHARED,則此時互斥量能夠用於同步多個進程,此時互斥量必須位於多個進程均可以訪問的共享內存中.接口:

    • int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr,int *pshared_ptr);獲取 pshared 屬性的值,並將其存入 pshared_ptr 中.

    • int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared_val);將 pshared_val 設置爲 pshared 屬性的值.

  • type,互斥量的類型,互斥量具備三種類型:PTHREAD_MUTEX_NORMAL,PTHREAD_MUTEX_ERRORCHECK,PTHREAD_MUTEX_RECURSIVE(遞歸),關於三種類型互斥量的區別,只須要了解這三種類型的互斥量在如下兩種操做下的行爲便可.見表:

    • 當互斥量爲 PTHREAD_MUTEX_RECURSIVE 時,則此時須要與加鎖一樣次數的解鎖動做才能釋放互斥量上的鎖,即若加鎖2次,此須要釋放鎖2次才能釋放互斥量上的鎖,以下:

pthread_mutexattr_t mutex_attr;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_settype(&mutex_attr,PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,&mutex_attr);
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);// 此時並無釋放 mutex 上的鎖.
pthread_mutex_unlock(&mutex);// 這裏才真正釋放了 mutex 上的鎖.

    • int pthread_mutexattr_gettype(const pthread_mutexattr_t *mutexattr,int *type);獲取互斥量的屬性,並將其存入 *type 中.

    • int pthread_mutexattr_settype(pthread_mutexattr_t *mutexattr,int type_val);將 type_val 設置爲互斥量的屬性.

讀寫鎖

  • 讀寫鎖,也是保護內存區域的那把鎖,有3個狀態,加讀鎖,加寫鎖,未加鎖.


接口

  • pthread_rwlock_t,數據類型,用來保存一個讀寫鎖對象.

  • int pthread_rwlock_init (pthread_rwlock_t * rwlock,const pthread_rwlockattr_t * attr);

  • int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);

  • int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);參見上圖'請求操做=加讀鎖'一列

  • int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);

  • int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);參見上圖'請求操做=加寫鎖'一列

  • int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);

  • int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);參見上圖'請求操做=釋放鎖'一列

屬性

  • pthread_rwlockattr_t,讀寫鎖的屬性存放在 pthread_rwlockattr_t 中,在調用 pthread_rwlock_init() 初始化一個讀寫鎖時能夠傳遞一個 pthread_rwlockattr_t 對象來設置讀寫鎖的屬性,接口:

    • int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

    • int pthread_rwlockattr_destroy(pthread_rwloackattr_t *attr);參見 pthread_mutexattr_init()/pthread_mutexattr_destroy(),這裏就不在再次說明了.

讀寫鎖的屬性以下:

  • pshared,同互斥量的 pshared 屬性同樣,接口:

    • int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int pshared_val);

    • int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr,int *pshared_ptr);

條件變量

  • 條件變量,線程使用條件變量來等待特定的條件發生,其上有2種操做:

    • 當條件未知足時,將線程掛起在條件變量上.

    • 當條件知足時,喚醒掛起在條件變量上的線程.

接口

  • int pthread_cond_init (pthread_cond_t *cond,const pthread_condattr_t *cond_attr);

  • int pthread_cond_destroy (pthread_cond_t *cond);

  • int pthread_cond_signal (pthread_cond_t *cond);喚醒掛起在 cond 上的一個線程.參見 pthread_cond_wait().

  • int pthread_cond_broadcast (pthread_cond_t *cond);喚醒掛起在 cond 上的全部線程.參見 pthread_cond_wait().

  • int pthread_cond_wait (pthread_cond_t *cond,pthread_mutex_t *mutex);釋放 mutex 上的鎖,將當前線程掛起在條件變量 cond 上,這2步是一個原子操做;直至被 signal()/broadcast() 喚醒;當喚醒時,首先獲取 mutex 上的鎖,而後再返回.

    • mutex 當前線程必須已經擁有 mutex 上的鎖,才能調用 pthread_cond_wait().

  • int pthread_cond_timedwait (pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);等同於 pthread_cond_wait(),只不過加了個超時機制;當超時時,首先獲取 mutex 上的鎖,而後返回.

    • abstime,是一個絕對時間,如要想掛起在 cond 上三分鐘,則應首先經過 gettimeofday() 獲取當前時間,而後再加上三分鐘,以下:

struct timeval current_time;
gettimeofday(&current_time,0);
struct timespec wait_time;
wait_time.tv_sec = current_time.tv_sec + 3*60;/* 當前時間+180s. */
wait_time.tv_nsec = current_time.tv_usec * 1000;
pthread_cond_timedwait(cond,mutex,&wait_time);

屬性

  • pthread_condattr_t,條件變量的屬性都存放在 pthread_condattr_t 對象中,接口:

    • int pthread_condattr_init(pthread_condattr_t *attr);

    • int pthread_condattr_destroy(pthread_condattr_t *attr);

條件變臉的屬性

  • pshared,同互斥量的 pshared 同樣,接口:

    • int pthread_condattr_getpshared(const pthread_condattr_t *attr,int *pshared_ptr);

    • int pthread_condattr_setpshared(pthread_condattr_t *attr,int pthread_val);

相關文章
相關標籤/搜索