線程特有數(shù)據(jù)(Thread Specific Data)

在單線程程序中,我們經(jīng)常要使用全局變量來實現(xiàn)多個函數(shù)間共享數(shù)據(jù)。在多線程環(huán)境下,由于數(shù)據(jù)空間是共享的目胡,因此全局變量也為所有線程所共有。但有時在應(yīng)用程序設(shè)計中有必要提供線程私有的全局變量诚隙,僅在某個線程中有效讶隐,但可以跨多個函數(shù)訪問,這樣每個線程訪問它自己獨立的數(shù)據(jù)空間久又,而不用擔(dān)心和其它線程的同步訪問巫延。

比如:在程序中每個線程都使用同一個指針?biāo)饕粋€鏈表,并在多個函數(shù)內(nèi)通過指針對鏈表進行操作地消,但是每個線程通過指針?biāo)饕逆湵矶际亲约邯氂械臄?shù)據(jù)炉峰。

線程特有數(shù)據(jù)(Thread Specific Data)

這樣在一個線程內(nèi)部的各個函數(shù)都能訪問、但其它線程不能訪問的變量脉执,我們就需要使用線程局部靜態(tài)變量(Static memory local to a thread) 同時也可稱之為線程特有數(shù)據(jù)(Thread-Specific Data 或 TSD)疼阔,或者線程局部存儲(Thread-Local Storage 或 TLS)。

POSIX 線程庫提供了如下 API 來管理線程特有數(shù)據(jù)(TSD):

1. 創(chuàng)建 key

int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))半夷;

第一參數(shù) key 指向 pthread_key_t 的對象的指針婆廊。請注意這里 pthread_key_t 的對象占用的空間是用戶事先分配好的,pthread_key_create 不會動態(tài)生成 pthread_key_t 對象巫橄。
第二參數(shù) desctructor淘邻,如果這個參數(shù)不為空,那么當(dāng)每個線程結(jié)束時湘换,系統(tǒng)將調(diào)用這個函數(shù)來釋放綁定在這個鍵上的內(nèi)存塊宾舅。

2. 動態(tài)數(shù)據(jù)初始化

有時我們在線程里初始化時,需要避免重復(fù)初始化彩倚。我們希望一個線程里只調(diào)用 pthread_key_create 一次筹我,這時就要使用 pthread_once與它配合。

int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))帆离;

第一個參數(shù) once_control 指向一個 pthread_once_t 對象蔬蕊,這個對象必須是常量 PTHREAD_ONCE_INIT,否則 pthread_once 函數(shù)會出現(xiàn)不可預(yù)料的結(jié)果哥谷。
第二個參數(shù) init_routine袁串,是調(diào)用的初始化函數(shù)概而,不能有參數(shù),不能有返回值囱修。
如果成功則返回0赎瑰,失敗返回非0值。

3. 鍵與線程數(shù)據(jù)關(guān)聯(lián)

創(chuàng)建完鍵后破镰,必須將其與線程數(shù)據(jù)關(guān)聯(lián)起來餐曼。關(guān)聯(lián)后也可以獲得某一鍵對應(yīng)的線程數(shù)據(jù)。關(guān)聯(lián)鍵和數(shù)據(jù)使用的函數(shù)為:

int pthread_setspecific(pthread_key_t *key, const void *value)鲜漩;

第一參數(shù) key 指向鍵源譬。
第二參數(shù) value 是欲關(guān)聯(lián)的數(shù)據(jù)。
函數(shù)成功則返回0孕似,失敗返回非0值踩娘。

注意:pthread_setspecific 為一個鍵指定新的線程數(shù)據(jù)時,并不會主動調(diào)用析構(gòu)函數(shù)釋放之前的內(nèi)存喉祭,所以調(diào)用線程必須自己釋放原有的線程數(shù)據(jù)以回收內(nèi)存养渴。

4. 獲取鍵管理的線程數(shù)據(jù)

獲取與某一個鍵關(guān)聯(lián)的數(shù)據(jù)使用函數(shù)的函數(shù)為:

void *pthread_getspecific(pthread_key_t *key);

參數(shù) key 指向鍵。
如果有與此鍵對應(yīng)的數(shù)據(jù)泛烙,則函數(shù)返回該數(shù)據(jù)理卑,否則返回NULL。

5. 刪除一個鍵

刪除一個鍵使用的函數(shù)為:

int pthread_key_delete(pthread_key_t key);

參數(shù) key 為要刪除的鍵蔽氨。
成功則返回0藐唠,失敗返回非0值。

注意:該函數(shù)將鍵設(shè)置為可用鹉究,以供下一次調(diào)用 pthread_key_create() 使用宇立。它并不檢查當(dāng)前是否有線程正在使用該鍵對應(yīng)的線程數(shù)據(jù),所以它并不會觸發(fā)函數(shù) pthread_key_create 中定義的 destructor 函數(shù)自赔,也就不會釋放該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存資源妈嘹,而且在將 key 設(shè)置為可用后,在線程退出時也不會再調(diào)用析構(gòu)函數(shù)匿级。所以在將 key 設(shè)置為可用之前蟋滴,必須要確定:

  1. 所有線程已經(jīng)析構(gòu) key 對應(yīng)的線程變量(要么顯示析構(gòu)染厅,要么線程退出);
  2. 不再使用該 key痘绎。 這兩個條件一般難以確定,所以實際使用時一般也不會將一個 key 設(shè)置為可用.

線程特有數(shù)據(jù)(TSD)的實現(xiàn)

在 Linux 中每個進程有一個全局的數(shù)組 __pthread_keys肖粮,數(shù)組中存放著 稱為 key 的結(jié)構(gòu)體孤页,定義類似如下:

struct pthread_key_struct {
    uintptr_t seq;
    void (*destructor)(void*);
} __pthread_keys[PTHREAD_KEYS_MAX];
__pthread_keys 數(shù)組.png

key 結(jié)構(gòu)中 seq 為一個序列號,用來作為使用標(biāo)志指示這個結(jié)構(gòu)在數(shù)組中是否正在使用涩馆,初始化時被設(shè)為0行施,即表示不在使用允坚。destructor 用來存放一個析構(gòu)函數(shù)指針。

如果 destructor 不為空蛾号,在線程退出時稠项,將 key 對應(yīng)的 TSD 作為參數(shù)調(diào)用析構(gòu)函數(shù),以釋放分配的緩存區(qū)鲜结。

pthread_create_key 會從數(shù)組中找到一個還未使用的 key 元素展运,將其序列號 seq 加1,并記錄析構(gòu)函數(shù)地址精刷,并將 key 在數(shù)組 __pthread_keys 中的下標(biāo)作為返回值返回拗胜。那么如何判斷一個 key 正在使用呢?

#define KEY_UNUSED(seq) (((seq) & 1) == 0)

如果 key 的序列號 seq 為偶數(shù)則表示未分配怒允,分配時將 seq 加1變成奇數(shù)埂软,即表示正在使用。這個操作過程采用原子 CAS 來完成纫事,以保證線程安全勘畔。在 pthread_key_delete() 時也將序列號 seq 加1,表示可以再被使用儿礼,通過序列號機制來保證回收的 key 不會被復(fù)用(復(fù)用 key 可能會導(dǎo)致線程在退出時可能會調(diào)用錯誤的析構(gòu)函數(shù))咖杂。但是一直加1會導(dǎo)致序列號回繞,還是會復(fù)用 key蚊夫,所以調(diào)用 pthread_create_key 獲取可用的 key 時會檢查是否有回繞風(fēng)險诉字,如果有則創(chuàng)建失敗。

#define KEY_USABLE(seq) (((uintptr_t)(seq)) < ((uintptr_t) ((seq) + 2)))

除了進程范圍內(nèi)的 key 結(jié)構(gòu)數(shù)組外知纷,系統(tǒng)還在進程中維護關(guān)于每個線程的控制塊 TCB(用于管理寄存器壤圃,線程棧等),里面有一個 pthread_key_data 類型的數(shù)組琅轧。這個數(shù)組中的元素數(shù)量和進程中的 key 數(shù)組數(shù)量相等伍绳。pthread_key_data 的定義類似如下:

struct pthread_key_data {
    uintptr_t seq;
    void* data;
};
線程的控制塊 TCB

根據(jù) pthread_key_create() 返回的可用的 key__pthread_keys 數(shù)組中的下標(biāo), pthread_setspecific()pthread_key_data 的數(shù)組 中定位相同下標(biāo)的一個元素 pthread_key_data乍桂,并設(shè)置其序號 seq 設(shè)置為對應(yīng)的 key 的序列號冲杀,數(shù)據(jù)指針 data 指向設(shè)置線程特有數(shù)據(jù)(TSD)的值。

pthread_getspecific() 用于將 pthread_setspecific() 設(shè)置的 data 取出睹酌。

實現(xiàn)線程局部數(shù)據(jù)的結(jié)構(gòu)圖

線程退出時权谁,pthread_key_data 中的序號 seq 用于判斷該 key 是否仍在使用中(即與在 __pthread_keys 中的同一個下標(biāo)對應(yīng)的 key 的序列號 seq 是否相同),若是則將 pthread_key_data 中 data(即 線程特有數(shù)據(jù) TSD)作為參數(shù)調(diào)用析構(gòu)函數(shù)憋沿。

pthread_key_delete 函數(shù)會將 key 的序列號 seq 加1旺芽,變成奇數(shù)(奇數(shù)表示該 key 可用),可能會使 key 的 seq 和 pthread_key_data 中的序號 seq 不相等,導(dǎo)致 pthread_key_data 中的線程私有數(shù)據(jù)無法釋放采章。所有在通過 pthread_key_delete 釋放一個 key 時运嗜,要保證線程特有數(shù)據(jù)已經(jīng)釋放。

由于系統(tǒng)在每個進程中 pthread_key_t 類型的數(shù)量是有限的悯舟,所有在進程中并不能獲取無限個 pthread_key_t 類型担租。Linux 中可以通過 PTHREAD_KEY_MAX(定義于 limits.h 文件中)或者系統(tǒng)調(diào)用 sysconf(_SC_THREAD_KEYS_MAX) 來確定當(dāng)前系統(tǒng)最多支持多少個 key。 Linux 中默認是 1024 個 key抵怎,這對大多數(shù)程序來書已經(jīng)夠了翩活。如果一個線程中有多個線程局部存儲變量(TLS),通潮愎螅可以將這些變量封裝到一個數(shù)據(jù)結(jié)構(gòu)中菠镇,然后使用封裝后的數(shù)據(jù)結(jié)構(gòu)和一個線程局部變量相關(guān)聯(lián),這樣就能減少對鍵值的使用承璃。

參考

https://blog.csdn.net/hustraiet/article/details/9857919
https://blog.csdn.net/hustraiet/article/details/9857919
https://blog.csdn.net/caigen1988/article/details/7901248
http://www.bidutools.com/?p=2443
https://spockwangs.github.io/blog/2017/12/01/thread-local-storage/
http://www.reibang.com/p/71c2f80d7bd1
https://blog.csdn.net/cywosp/article/details/26469435
http://www.embeddedlinux.org.cn/emblinuxappdev/117.htm

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末利耍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盔粹,更是在濱河造成了極大的恐慌隘梨,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舷嗡,死亡現(xiàn)場離奇詭異轴猎,居然都是意外死亡,警方通過查閱死者的電腦和手機进萄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門捻脖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人中鼠,你說我怎么就攤上這事可婶。” “怎么了援雇?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵矛渴,是天一觀的道長。 經(jīng)常有香客問我惫搏,道長具温,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任筐赔,我火速辦了婚禮铣猩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘川陆。我一直安慰自己剂习,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布较沪。 她就那樣靜靜地躺著鳞绕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尸曼。 梳的紋絲不亂的頭發(fā)上们何,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音控轿,去河邊找鬼冤竹。 笑死,一個胖子當(dāng)著我的面吹牛茬射,可吹牛的內(nèi)容都是我干的鹦蠕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼在抛,長吁一口氣:“原來是場噩夢啊……” “哼钟病!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刚梭,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肠阱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后朴读,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屹徘,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年衅金,在試婚紗的時候發(fā)現(xiàn)自己被綠了噪伊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡氮唯,死狀恐怖酥宴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情您觉,我是刑警寧澤拙寡,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站琳水,受9級特大地震影響肆糕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜在孝,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一诚啃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧私沮,春花似錦始赎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魔招。三九已至,卻和暖如春五辽,著一層夾襖步出監(jiān)牢的瞬間办斑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工杆逗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乡翅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓罪郊,卻偏偏與公主長得像蠕蚜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悔橄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容