執行緒管理
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
thrd_create
建立一個新執行緒,該執行緒的工作就是執行func(arg)
呼叫,程式設計師需要為執行緒編寫一個函式,函式簽名為:thrd_start_t
,即int (*)(void*)
型別的函式。新建立的執行緒的識別符號存放在thr
內。
thrd_t thrd_current(void);
thrd_current
函式返回撥用執行緒的識別符號。
int thrd_detach(thrd_t thr);
thrd_detach
會通知作業系統,當該執行緒結束時由作業系統負責回收該執行緒所佔用的資源。
int thrd_equal(thrd_t thr0, thrd_t thr1);
thrd_equal
用於判斷兩個執行緒識別符號是否相等(即標識同一執行緒),thrd_t
是標準約定的型別,可能是一個基礎型別,也可能會是結構體,開發人員應該使用thrd_equal
來判斷兩者是否相等,不能直接使用==
。即便==
在某個平臺下表現出來是正確的,但它不是標準的做法,也不可跨平臺。
void thrd_exit(int res)
thrd_exit
函式提早結束當前執行緒,res
為它的退出狀態碼。這與程序中的exit
函式類似。
int thrd_join(thrd_t thr, int *res)
thrd_join
將阻塞當前執行緒,直到執行緒thr
結束時才返回。如果res
非空,那麼res
將儲存thr
執行緒的結束狀態碼。如果某一執行緒內沒有呼叫thrd_detach
函式將自己設定為detach
狀態,那麼當它結束時必須由另外一個執行緒呼叫thrd_join
函式將它留下的僵死狀態變為結束,並回收它所佔用的系統資源。
void thrd_sleep(const xtime *xt)
thrd_sleep
函式讓當前執行緒中途休眠,直到由xt
指定的時間過去後才醒過來。
void thrd_yield(void)
thrd_yield
函式讓出CPU給其它執行緒或程序。
互斥物件和函式
threads.h中提供了豐富的互斥物件,使用者只需在mtx_init
初始化時,指定該互斥物件的型別即可。
int mtx_int(mtx_t *mtx, int type);
mtx_init
函式用於初始化互斥物件,type
決定互斥物件的型別,一共有下面6種型別:
- mtx_plain –簡單的,非遞迴互斥物件
- mtx_timed –非遞迴的,支援超時的互斥物件
- mtx_try –非遞迴的,支援鎖檢測的互斥物件
- mtx_plain | mtx_recursive –簡單的,遞迴互斥物件
- mtx_timed | mtx_recursive –支援超時的遞迴互斥物件
- mtx_try | mtx_recursive –支援鎖檢測的遞迴互斥物件
int mtx_lock(mtx_t *mtx) int mtx_timedlock(mtx_t *mtx, const xtime *xt) int mtx_trylock(mtx_t *mtx)
mtx_xxxlock
函式對mtx
互斥物件進行加鎖 , 它們會阻塞,直到獲取鎖,或者xt
指定的時間已過去。而trylock
版本會進行鎖檢測,如果該鎖已被其它執行緒佔用,那麼它馬上返回thrd_busy
。
int mtx_unlock(mtx_t *mtx)
mtx_unlock
對互斥物件mtx
進行解鎖。
條件變數
threads.h透過mtx
物件和條件變數來實現wait-notify
機制。
int cnd_init(cnd_t *cond)
初始化條件變數,所有條件變數必須初始化後才能使用。
int cnd_wait(cnd_t *cond, mtx_t *mtx) int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const xtime *xt)
cnd_wait
函式自動對mtx
互斥物件進行解鎖操作,然後阻塞,直到條件變數cond
被cnd_signal
或cnd_broadcast
呼叫喚醒,當前執行緒變為非阻塞時,它將在返回之前鎖住mtx
互斥物件。cnd_timedwait
函式與cnd_wait
類似,例外之處是當前執行緒在xt
時間點上還未能被喚醒時,它將返回,此時返回值為thrd_timeout
。cnd_wait
和cnd_timedwait
函式在被呼叫前,當前執行緒必須鎖住mtx
互斥物件。
int cnd_signal(cnd_t *cond) int cnd_broadcast(cnd_t *cond)
cnd_broadcast
喚醒那些當前已經阻塞在cond
條件變數上的所有執行緒,而cnd_signal
只喚醒其中之一。
void cnd_destroy(cnd_t *cond)
銷燬條件變數。
初始化函式
試想一下,如何在一個多執行緒同時執行的環境下來初始化一個變數,即著名的延遲初始化單例模式。你可能會使用DCL
技術。但在C11中,你可以直接使用call_once
函來實現。
void call_once(once_flag *flag, void (*func)(void))
call_once
函式使用flag
來保確func
只被呼叫一次。第一個執行緒使用flag
去呼叫call_once
時,函式func
會被呼叫,而接下來的使用相同flag
來呼叫的call_once
,func
均不會再次被呼叫,以保正func
在多執行緒環境只被呼叫一次。
執行緒專有資料(TSD
) 和執行緒區域性資料 (TLS
)
在多執行緒開發中,並不是所有的同步都需要加鎖的,有時巧妙的資料分解也可減少鎖的碰撞。每個執行緒都擁有自己私有資料,使用它可以減少執行緒間共享資料之間的同步開銷。
如果要將一些遺留程式碼進行執行緒化,很多函式都使用了全域性變數,而在多執行緒環下,最好的方法可能是將這些全域性量變數換成執行緒私有的全域性變數即可。
TSD
和TLS
就是專門用來處理執行緒私有資料的。 它的生存週期是整個執行緒的生存週期,但它在每個執行緒都有一份複製,每個執行緒只能read-write-update
屬於自己的那份。如果透過指標方式來read-write-update
其它執行緒的備份,它的行為是未定義的。
TSD
可認為執行緒私有記憶體下的void *
組數,每個資料項的key
對應於陣列的下標,用於索引功能。當一個新執行緒建立時,執行緒的TSD
區域將所有key
關聯的值設定為NULL
。TSD
是透過函式的方式來操作的。C11
中TSD
提供的標準函式如下:
int tss_create(tss_t *key, tss_dtor_t dtor) void tss_delete(tss_t key) void *tss_get(tss_t key) int tss_set(tss_t key, void *val)
tss_create
函式建立一個key
,dtor
為該key
將要關聯value
的解構函式。當執行緒退出時,會呼叫dtor
函式來釋放該key
關聯的value
所佔用的資源,當然,如果退出時value
值為NULL
,dtor
將不被呼叫。tss_delete
函式刪除一個key
,tss_get
/tss_set
分別獲得或設定該key
所關聯的value
。
透過上述TSD
來操作執行緒私有變數的方式,顯得相對繁瑣; C11提供了TLS
方法,可以像一般變數的方式去訪問執行緒私有變數。做法很簡單,在宣告和定義執行緒私變數時指定_Thread_local
儲存修飾符即可,關於_Thread_local
,C11 有如下的描述:
- 在宣告式中,
_Thread_local
只能單獨使用,或者跟static
或extern
一起使用。 - 在某一區快中宣告某一物件,如果宣告儲存修飾符有
_Thread_local
,那麼必須同時有static
或extern
。 - 如果
_Thread_local
出現在一物件的某個宣告式中,那麼此物件的其餘各處宣告式都應該有_Thread_local
儲存修飾符。 - 如果某一物件的宣告式中出現
_Thread_local
儲存修飾符,那麼它有執行緒儲存期。該物件的生命週期為執行緒的整個執行週期,它線上程出生時建立,並線上程啟動時初始化。每個執行緒均有一份該物件,使用宣告時的名字即可引用正在執行當前表示式的執行緒所關聯的那個物件。
TLS
方式與傳統的全域性變數或static
變數的使用方式完全一致,不同的是,TLS
變數在不同的執行緒上均有各自的一份。執行緒訪問TLS
時不會產生data race
,因為不需要任何加鎖機制。TLS
方式需要編譯器的支援,對於任何_Thread_local
變數,編譯器要將之編譯並生成放到各個執行緒的private memory
區域,並且訪問這些變數時,都要獲得當前執行緒的資訊,從而訪問正確的物理物件,當然這一切都是在連結過程早已安排好的。