[APUE chapter 12] 執行緒控制
阿新 • • 發佈:2019-01-02
作者:isshe
日期:2016.10.30
郵箱:[email protected]
github: https://github.com/isshe
1.前言
2. 相關概念
- 執行緒分離:
- 就是說與建立執行緒分離,當新建執行緒執行結束,就終止執行緒並釋放資源。
- 預設情況下是不執行緒分離的,此時新執行緒執行結束後如果建立執行緒沒有結束就等待建立執行緒結束,或者使用pthread_join()才能終止新執行緒,並回收資源。
- 執行緒分離方法:(通常在不關心執行緒終止狀態時候使用)
- 使用pthread_detach函式,執行緒退出時回收資源。
- 從一開始就設定執行緒屬性為執行緒分離:修改pthread_attr_t結構中的detachstate執行緒屬性。
2.1 執行緒屬性
- 在編譯階段使用_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE來檢查系統是否支援執行緒棧屬性。
- 在執行階段使用_SC_THREAD_ATTR_STACKADDR 和 _SC_THREAD_ATTR_STACKSIZE引數呼叫sysconf檢查系統是否支援執行緒棧屬性。
- 當修改執行緒屬性stackaddr時,會使警戒區無效,即guardsize==0。
- 互斥量用於保護條件變數關聯的條件。在阻塞執行緒之前,pthread_cond_wait和pthread_cond_timewait函式釋放與條件相關的互斥量。(注意!!!這個可以解答上一個博文的疑問)
2.2 重入
10.6節討論了可重入函式和訊號處理函式,執行緒在這兩個方面也是類似的。
- 如果一個函式對多個執行緒來說是可重入的,則這個函式是執行緒安全的。(但不能說明對訊號處理程式來說是可重入的)
- 執行緒安全:如果一個函式在同一個時間點被多個執行緒安全地呼叫,則此函式是執行緒安全的。
- 測試作業系統是否支援執行緒安全函式的方法:
- 編譯時,測試_POSIX_THREAD_SAFE_FUNCTIONS。
- 執行時,sysconf函式傳入_SC_THREAD_SAFE_FUNCTIONS引數。
- 造成執行緒不安全的情況:
- 函式返回的資料存放在靜態的記憶體緩衝區中。 *
2.3 執行緒私有資料
- 執行緒私有資料也叫執行緒特定資料(thread-specific data),是儲存和查詢某個特定執行緒相關資料的一種機制。
3. 相關函式
3.1 執行緒屬性相關
pthread_attr_init和pthread_attr_destroy
- 原型:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
- 功能:
- init:初始化一個pthread_attr_t結構。
- destroy:反初始化pthread_attr_t結構。
- 引數:
- attr:指向pthread_attr_t 結構的指標。
- 注意:
- 使用pthread_attr_init後,attr指向的結構的內容就是當前作業系統的執行緒屬性的預設值。
- 如果pthread_attr_inin**的實現**對屬性物件的記憶體空間是動態分配的,則pthread_attr_destroy會釋放記憶體空間。
pthread_attr_getdetachstate 和 pthread_attr_setdetachstate
- 原型:
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
- 功能:
- setdetachstate:設定pthread_attr_t結構的detachstate屬性為PTHREAD_CREATE_DETACHED或者PTHREAD_CREATE_JOINABLE。
- getdetachstate:獲取detachstate屬性。(從attr到detachstate)
- 引數:
- attr:指向pthread_attr_t結構的指標。
- detachstate: 指向儲存分離狀態的指標變數。(獲取到的狀態存到此變數指向的記憶體中)
pthread_attr_getstack 和 pthread_attr_setstack
- 原型:
#include <pthread.h>
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr,
void **stackaddr, size_t *stacksize);
- 功能:設定/獲取執行緒棧屬性。
- 引數:
- attr:指向pthread_attr_t結構的指標。
- stackaddr:棧的最低記憶體地址,但並不一定是棧的開始位置。
- stacksize:棧大小。
- 返回值:成功0, 否則錯誤編號。
pthread_attr_getstacksize 和 pthread_attr_setstacksize
- 原型:
#include <pthread.h>
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
- 功能:設定/獲取執行緒屬性stacksize。
- 引數:略。
- 返回:成功0,否則錯誤編號。
pthread_attr_getguardsize 和 pthread_attr_setguardsize
- 原型:
#include <pthread.h>
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
- 功能:設定/獲取警戒區域大小。(用以避免棧溢位,通常值為系統頁大小)
- 返回:成功0, 否則錯誤編號。
- 注意:
- 當修改執行緒屬性stackaddr時,會使警戒區無效,即guardsize==0。
- 如果guardsize執行緒屬性被修改了,作業系統可能會把它取為頁大小的整數倍。
3.2 同步屬性
- 這個知識點不大理解。p345-p348
pthread_mutexattr_*(互斥量屬性)
- 原型:
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *kind);
int pthread_mutexattr_getpshared(const pthread_mutexattr_t
*restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,
int pshared);
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict
attr, int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,
int robust);
- 功能:
- init: 初始化。
- destroy: 反初始化。
- getpshared:獲取程序共享屬性。
- setpshared:設定程序共享屬性。
- getrobust:獲取健壯的互斥量屬性的值。
- setrobust:設定健壯的互斥量屬性的值。
- gettype:獲取互斥量型別屬性。
- settype:設定互斥量型別屬性。
- 型別互斥量屬性控制這互斥量的鎖定特性。
- 互斥量型別行為:
互斥量 沒有解鎖時重新加鎖 不佔有時解鎖 已解鎖時解鎖 PTHREAD_MUTEX_NORMAL 死鎖 未定義 未定義 PTHREAD_MUTEX_ERRORCHECK 返回錯誤 返回錯誤 返回錯誤 PTHREAD_MUTEX_RECURSIVE 允許 返回錯誤 返回錯誤 PTHREAD_MUTEX_DEFAULT 未定義 未定義 未定義 * 不佔有時解鎖:一個執行緒解鎖被另一個執行緒執行緒加鎖的互斥量。
- 返回:成功0, 否則錯誤編號。
pthread_rwlockattr_*(讀寫鎖屬性)
- 原型:
#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t
*restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,
int pshared);
- 功能:
- getpshared:獲取程序共享屬性。(讀寫鎖唯一支援的屬性)
- setpshared:設定程序共享屬性。
- 引數:略。
- 返回值:成功0, 否則錯誤編號。
pthread_condattr_(條件變數屬性)
- Single UNIX Specification目前定義了條件變數的兩個屬性:程序共享屬性和時鐘屬性。
- 原型:
#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);
- 功能:
- getclock:獲取可被用於pthread_cond_timedwait函式的時鐘ID。(注意在使用pthread_cond_timedwait前需要用pthread_condattr_t物件對條件變數程序初始化)
- setclock:對時鐘ID進行修改。
- 引數:
- clock_id:時鐘ID。
- 返回:成功0, 否則錯誤編號。
pthread_barrierattr_*(屏障屬性)
- 原型:
#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
- 功能:略。
- 引數:略。
- 返回:成功0,否則錯誤編號。
3.3 重入
lockfile相關
- 原型:
void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);
- 功能:鎖定標準io的FILE。(注意這個鎖是遞迴的,也就是可以多次鎖定的)
- 引數:略
- 返回:
- ftrylockfile:成功0,若不能獲取鎖,返回非0值。
- 其他兩個沒有返回值。
unlocked的字元操作
為了處理每讀寫一個字元就獲取鎖和釋放鎖一次的開銷,可以使用下面函式。(p356)
* 原型:(其實有好多這種函式,只列出書上列出的幾個,其他man手冊有)
#include <stdio.h>
int getc_unlocked(FILE *stream);
int getchar_unlocked(void);
int putc_unlocked(int c, FILE *stream);
int putchar_unlocked(int c);
- 功能:略。
- 引數:略。
- 返回:略。
3.4 執行緒私有資料
在分配執行緒特定資料之前,需要建立與該資料關聯的鍵。
pthread_key相關
- 原型:
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destr_function)(void *));
int pthread_key_delete(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *pointer);
void * pthread_getspecific(pthread_key_t key);
- 功能:(p359和p360的兩個建立鍵的方法要再學習)
- create:建立一個鍵,還未與執行緒私有資料值關聯。(這個鍵可以被程序中的所有執行緒使用,但每個執行緒把這個鍵與不同的執行緒私有(特定)資料地址關聯。
- delete:取消鍵與執行緒私有(特定)資料值的關聯關係。
- setspecific:把鍵和執行緒特定資料關聯起來。
- getspecific:獲取執行緒私有資料的地址。
- 引數:
- key:指向鍵的指標。(這是一個值-結果引數)
- destructor:解構函式,沒有則設為NULL。(pthread_exit, 執行緒執行返回, 正常退出是會呼叫,exit/_exit/_Exit/abort不會呼叫,執行緒取消時,只有在最後的清理處理程式執行返回後,解構函式才會執行)
- pointer:指向需要關聯的私有資料地址的指標。
- 返回:0或錯誤編號。
3.5 取消選項
- 有兩個執行緒屬性並沒有包含在pthread_attr_t結構中:可取消狀態和可取消型別。
- 這兩個屬性影響這執行緒在相應pthread_cancel函式呼叫時所呈現的行為。
- 可取消狀態有:PTHREAD_CANCEL_DISABLE 和 PTHREAD_CANCEL_ENABLE。
- 可取消型別有:PTHREADCANEL_DEFERRED 和 PTHREAD_CANCEL_ASYNCHRONOUS。
- 推遲取消:在遇到取消點才能取消。(取消點在p362)
- 非同步取消:可以在任意時候取消。
pthread_setcancelstate和pthread_setcanceltype
- 原型:
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
- 功能:
- pthread_setcancelstate:修改可取消狀態。
- pthread_setcanceltype:修改可取消型別。
- 引數:略。
- 返回:0或錯誤編號。
pthread_testcancel
- 原型:
#include <pthread.h>
void pthread_testcancel(void);
- 功能:新增自己的取消點。
3.8 執行緒和訊號
- 每個執行緒都有自己的訊號遮蔽字(但會繼承父執行緒的),但訊號的處理是程序中所有執行緒共享的。
- 意味著:某執行緒修改某訊號的處理行為後,所有執行緒都共享這個處理行為的改變。
- 程序用sigprocmask函式來阻止訊號傳送。
- 執行緒用pthread_sigmask。
- 為避免錯誤,執行緒在呼叫sigwait**之前**,必須阻塞那些它正在等待的訊號。(返回前恢復)
- 把訊號傳送給程序用:kill。
- 把訊號傳送給執行緒用:pthread_kill。
- 鬧鐘定時器是程序資源,並且所有的執行緒共享相同的鬧鐘。
- 如果一個訊號的動作是終止程序,把此訊號傳遞給某個執行緒仍然會殺死整個程序。
pthread_sigmask
- 原型:
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
- 功能:
- 設定遮蔽訊號集(當set不為NULL時)。
- 獲取遮蔽訊號集(當set為NULL且oldset不為NULL時)。
- 引數:
- how:
- SIG_BLOCK:把訊號機新增到執行緒訊號遮蔽字中。
- SIG_SETMASK:用訊號集替換執行緒的訊號遮蔽字。
- SIG_UNBLOCK:從執行緒訊號遮蔽字中移除訊號集。
- set:目標操作訊號遮蔽集。
- oldset:原來的訊號遮蔽集。
- how:
sigwait
- 原型:
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);
- 功能:等待一個或多個訊號出現。
- 引數:
- set:指定執行緒等待的訊號集。
- sig:返回時,sig指向的整數將包含傳送訊號的數量。(???)
- 返回:0或錯誤編號。
pthread_kill
- 原型:
#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
- 功能:
- 傳送訊號給執行緒。
- sig==0時,檢查執行緒是否存在。
3.9 執行緒和fork
- fork後,子程序會繼承互斥量,讀寫鎖,條件變數等。(具體見部落格:待補)
- 如果父程序有一個以上執行緒,fork後,如果子程序接著不exec,就要清理鎖狀態。(子程序內部只有一個執行緒)
- 可多次呼叫pthread_atfork函式從而設定多套fork處理函式(疑問:多套如何工作?)。
- 使用多套時,處理程式的呼叫順序是不同的:(這樣可以允許多個模組註冊它們自己的fork處理函式,而且可以保持鎖的層次)
- parent和child 是以它們註冊時的順序進行呼叫。
- prepare的呼叫順序則和它們註冊時相反。
pthread_atfork
- 原型:
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void(*child)(void));
- 功能:通過此函式建立fork處理函式清除鎖狀態。(最多可安裝3個清理鎖的函式)
- 引數:
- prepare:由父程序fork建立子程序前呼叫,任務是獲取父程序定義的所有鎖。
- parent:fork建立子程序之後、返回之前在父程序上下文中呼叫,任務是對prepare中獲取的所有鎖進行解鎖。
- child:在fork返回前在子程序上下文中呼叫,任務和parent一樣。
- 注意:不會出現加鎖一次解鎖兩次的情況,prepare獲取的鎖在fork之後就有了副本。
4. 拓展知識
- 對程序來說,虛地址空間的大小是固定的。
4.1 pthread_once
- 原型:
#include <pthread.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
- 功能:只進程一次初始化。
- 引數:
- once_contral:必須是一個非本地變數(如全域性變數或靜態變數)而且必須初始化為PTHREAD_ONCE_INIT。
5. 疑問
- 什麼時程序共享屬性?
- 控制著條件變數時可以被程序的國歌執行緒使用,還是可以被多個程序的執行緒使用。
- 值可為:PTHREAD_PROCESS_SHARED(多程序中的多執行緒可用) 和 PTHREAD_PROCESS_PRIVATE(初始化該屏障的程序內可以)。
- 還要再深入。
*
6. 習題
12.1 在linux系統中執行圖12-17,把輸出結果重定向到檔案,解釋結果。
- 終端輸出和重定向結果:
- 這是一個行緩衝和全緩衝的問題。
- 標準輸出定向到終端時,是行緩衝,每次列印一行。
- 標準輸出定向到檔案時,是全緩衝,fork的時候,複製了緩衝區內容,故而有此結果(當緩衝區滿或關閉檔案的時候,沖洗緩衝區)
12.5 假設可以在一個程式中建立多個執行緒執行不同的任務,為什麼還是可能會需要fork?
- 可能需要在程式中執行另一個程式(exec)
- 可能單個程序受到某些限制(如預設單程序最多能開啟1024個檔案)
7. 參考資料
- 《unix環境高階程式設計》第12章