日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
詳解Linux線程局部存儲

在Linux系統(tǒng)中使用C/C++進(jìn)行多線程編程時,我們遇到最多的就是對同一變量的多線程讀寫問題,大多情況下遇到這類問題都是通過鎖機(jī)制來處理,但這對程序的性能帶來了很大的影響,當(dāng)然對于那些系統(tǒng)原生支持原子操作的數(shù)據(jù)類型來說,我們可以使用原子操作來處理,這能對程序的性能會得到一定的提高。那么對于那些系統(tǒng)不支持原子操作的自定義數(shù)據(jù)類型,在不使用鎖的情況下如何做到線程安全呢?本文將從線程局部存儲方面,簡單講解處理這一類線程安全問題的方法。

申扎網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)從2013年成立到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運(yùn)維經(jīng)驗,來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。

一、數(shù)據(jù)類型

在C/C++程序中常存在全局變量、函數(shù)內(nèi)定義的靜態(tài)變量以及局部變量,對于局部變量來說,其不存在線程安全問題,因此不在本文討論的范圍之內(nèi)。全局變量和函數(shù)內(nèi)定義的靜態(tài)變量,是同一進(jìn)程中各個線程都可以訪問的共享變量,因此它們存在多線程讀寫問題。在一個線程中修改了變量中的內(nèi)容,其他線程都能感知并且能讀取已更改過的內(nèi)容,這對數(shù)據(jù)交換來說是非??旖莸模怯捎诙嗑€程的存在,對于同一個變量可能存在兩個或兩個以上的線程同時修改變量所在的內(nèi)存內(nèi)容,同時又存在多個線程在變量在修改的時去讀取該內(nèi)存值,如果沒有使用相應(yīng)的同步機(jī)制來保護(hù)該內(nèi)存的話,那么所讀取到的數(shù)據(jù)將是不可預(yù)知的,甚至可能導(dǎo)致程序崩潰。 如果需要在一個線程內(nèi)部的各個函數(shù)調(diào)用都能訪問、但其它線程不能訪問的變量,這就需要新的機(jī)制來實現(xiàn),我們稱之為Static memory local to a thread (線程局部靜態(tài)變量),同時也可稱之為線程特有數(shù)據(jù)(TSD: Thread-Specific Data)或者線程局部存儲(TLS: Thread-Local Storage)。這一類型的數(shù)據(jù),在程序中每個線程都會分別維護(hù)一份變量的副本(copy),并且長期存在于該線程中,對此類變量的操作不影響其他線程。如下圖:

二、一次性初始化

在講解線程特有數(shù)據(jù)之前,先讓我們來了解一下一次性初始化。多線程程序有時有這樣的需求:不管創(chuàng)建多少個線程,有些數(shù)據(jù)的初始化只能發(fā)生一次。列如:在C++程序中某個類在整個進(jìn)程的生命周期內(nèi)只能存在一個實例對象,在多線程的情況下,為了能讓該對象能夠安全的初始化,一次性初始化機(jī)制就顯得尤為重要了?!谠O(shè)計模式中這種實現(xiàn)常常被稱之為單例模式(Singleton)。Linux中提供了如下函數(shù)來實現(xiàn)一次性初始化:

#include

// Returns 0 on success, or a positive error number on error

int pthread_once (pthread_once_t *once_control, void (*init) (void));

利用參數(shù)once_control的狀態(tài),函數(shù)pthread_once()可以確保無論有多少個線程調(diào)用多少次該函數(shù),也只會執(zhí)行一次由init所指向的由調(diào)用者定義的函數(shù)。init所指向的函數(shù)沒有任何參數(shù),形式如下:

void init (void)

{

// some variables initializtion in here

}

另外,參數(shù)once_control必須是pthread_once_t類型變量的指針,指向初始化為PTHRAD_ONCE_INIT的靜態(tài)變量。在C++0x以后提供了類似功能的函數(shù)std::call_once (),用法與該函數(shù)類似。

三、線程局部數(shù)據(jù)API

在Linux中提供了如下函數(shù)來對線程局部數(shù)據(jù)進(jìn)行操作

#include

// Returns 0 on success, or a positive error number on error

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

// Returns 0 on success, or a positive error number on error

int pthread_key_delete (pthread_key_t key);

// Returns 0 on success, or a positive error number on error

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

// Returns pointer, or NULL if no thread-specific data is associated with key

void *pthread_getspecific (pthread_key_t key);

函數(shù)pthread_key_create()為線程局部數(shù)據(jù)創(chuàng)建一個新鍵,并通過key指向新創(chuàng)建的鍵緩沖區(qū)。因為所有線程都可以使用返回的新鍵,所以參數(shù)key可以是一個全局變量(在C++多線程編程中一般不使用全局變量,而是使用單獨的類對線程局部數(shù)據(jù)進(jìn)行封裝,每個變量使用一個獨立的pthread_key_t)。destructor所指向的是一個自定義的函數(shù),其格式如下:

void Dest (void *value)

{

// Release storage pointed to by 'value'

}

只要線程終止時與key關(guān)聯(lián)的值不為NULL,則destructor所指的函數(shù)將會自動被調(diào)用。如果一個線程中有多個線程局部存儲變量,那么對各個變量所對應(yīng)的destructor函數(shù)的調(diào)用順序是不確定的,因此,每個變量的destructor函數(shù)的設(shè)計應(yīng)該相互獨立。 函數(shù)pthread_key_delete()并不檢查當(dāng)前是否有線程正在使用該線程局部數(shù)據(jù)變量,也不會調(diào)用清理函數(shù)destructor,而只是將其釋放以供下一次調(diào)用pthread_key_create()使用。在Linux線程中,它還會將與之相關(guān)的線程數(shù)據(jù)項設(shè)置為NULL。 由于系統(tǒng)對每個進(jìn)程中pthread_key_t類型的個數(shù)是有限制的,所以進(jìn)程中并不能創(chuàng)建無限個的pthread_key_t變量。Linux中可以通過PTHREAD_KEY_MAX(定義于limits.h文件中)或者系統(tǒng)調(diào)用sysconf(_SC_THREAD_KEYS_MAX)來確定當(dāng)前系統(tǒng)最多支持多少個鍵。Linux中默認(rèn)是1024個鍵,這對于大多數(shù)程序來說已經(jīng)足夠了。如果一個線程中有多個線程局部存儲變量,通??梢詫⑦@些變量封裝到一個數(shù)據(jù)結(jié)構(gòu)中,然后使封裝后的數(shù)據(jù)結(jié)構(gòu)與一個線程局部變量相關(guān)聯(lián),這樣就能減少對鍵值的使用。 函數(shù)pthread_setspecific()用于將value的副本存儲于一數(shù)據(jù)結(jié)構(gòu)中,并將其與調(diào)用線程以及key相關(guān)聯(lián)。參數(shù)value通常指向由調(diào)用者分配的一塊內(nèi)存,當(dāng)線程終止時,會將該指針作為參數(shù)傳遞給與key相關(guān)聯(lián)的destructor函數(shù)。當(dāng)線程被創(chuàng)建時,會將所有的線程局部存儲變量初始化為NULL,因此第一次使用此類變量前必須先調(diào)用pthread_getspecific()函數(shù)來確認(rèn)是否已經(jīng)于對應(yīng)的key相關(guān)聯(lián),如果沒有,那么pthread_getspecific()會分配一塊內(nèi)存并通過pthread_setspecific()函數(shù)保存指向該內(nèi)存塊的指針。 參數(shù)value的值也可以不是一個指向調(diào)用者分配的內(nèi)存區(qū)域,而是任何可以強(qiáng)制轉(zhuǎn)換為void的變量值,在這種情況下,先前的pthread_key_create()函數(shù)應(yīng)將參數(shù) destructor設(shè)置為NULL 函數(shù)pthread_getspecific()正好與pthread_setspecific()相反,其是將pthread_setspecific()設(shè)置的value取出。在使用取出的值前最好是將void轉(zhuǎn)換成原始數(shù)據(jù)類型的指針。

四、深入理解線程局部存儲機(jī)制

\1. 深入理解線程局部存儲的實現(xiàn)有助于對其API的使用。在典型的實現(xiàn)中包含以下數(shù)組: pthread_key_create()返回的pthread_key_t類型值只是對全局?jǐn)?shù)組的索引,該全局?jǐn)?shù)組標(biāo)記為pthread_keys,其格式大概如下:

數(shù)組的每個元素都是一個包含兩個字段的結(jié)構(gòu),第一個字段標(biāo)記該數(shù)組元素是否在用,第二個字段用于存放針對此鍵、線程局部存儲變的解構(gòu)函數(shù)的一個副本,即destructor函數(shù)。 \2. 在常見的存儲pthread_setspecific()函數(shù)參數(shù)value的實現(xiàn)中,大多數(shù)都類似于下圖的實現(xiàn)。圖中假設(shè)pthread_keys[1]分配給func1()函數(shù),pthread API為每個函數(shù)維護(hù)指向線程局部存儲數(shù)據(jù)塊的一個指針數(shù)組,其中每個數(shù)組元素都與圖線程局部數(shù)據(jù)鍵的實現(xiàn)(上圖)中的全局pthread_keys中元素一一對應(yīng)。

五、總結(jié)

使用全局變量或者靜態(tài)變量是導(dǎo)致多線程編程中非線程安全的常見原因。在多線程程序中,保障非線程安全的常用手段之一是使用互斥鎖來做保護(hù),這種方法帶來了并發(fā)性能下降,同時也只能有一個線程對數(shù)據(jù)進(jìn)行讀寫。如果程序中能避免使用全局變量或靜態(tài)變量,那么這些程序就是線程安全的,性能也可以得到很大的提升。如果有些數(shù)據(jù)只能有一個線程可以訪問,那么這一類數(shù)據(jù)就可以使用線程局部存儲機(jī)制來處理,雖然使用這種機(jī)制會給程序執(zhí)行效率上帶來一定的影響,但對于使用鎖機(jī)制來說,這些性能影響將可以忽略。更高性能的線程局部存儲機(jī)制就是使用__thread,這個以后再討論。


名稱欄目:詳解Linux線程局部存儲
瀏覽地址:http://www.5511xx.com/article/ccehchs.html