執行緒局部存儲

執行緒局部存儲

英文為Thread Local Storage ,縮寫為TLS。為什麼要有TLS?原因在於,全局變數與函式內定義的靜態變數,是各個執行緒都可以訪問的共享變數。

介紹

執行緒局部存儲 執行緒局部存儲

在一個執行緒修改的記憶體內容,對所有執行緒都生效。這是一個優點也是一個缺點。說它是優點,執行緒的數據交換變得非常快捷。說它是缺點,一個執行緒死掉了,其它執行緒也性命不保; 多個執行緒訪問共享數據,需要昂貴的同步開銷,也容易造成同步相關的BUG。

如果需要在一個執行緒內部的各個函式調用都能訪問、但其它執行緒不能訪問的變數(被稱為static memory local to a thread 執行緒局部靜態變數),就需要新的機制來實現。這就是TLS。

執行緒局部存儲在不同的平台有不同的實現,可移植性不太好。幸好要實現執行緒局部存儲並不難,最簡單的辦法就是建立一個全局表,通過當前執行緒ID去查詢相應的數據,因為各個執行緒的ID不同,查到的數據自然也不同了。但Windows系統採用了每個執行緒建執行緒專享的索引表,表的條目為執行緒局部存儲的地址。線上程執行的任何代碼處,都可以查詢本執行緒的這個索引表獲得要訪問的執行緒局部存儲的地址。

大多數平台都提供了執行緒局部存儲的方法,無需要我們自己去實現:

實現

linux

實現

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

int pthread_key_delete(pthread_key_t key);

void *pthread_getspecific(pthread_key_t key);

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

Win32

實現

方法

方法一

(Kernel32 API)

每個執行緒創建時系統給它分配一個LPVOID指針的數組(叫做TLS索引數組),這個數組從C編程角度是隱藏著的不能直接訪問(實際上該數組地址寫入了執行緒信息塊 thread information block,縮寫TIB或TEB),需要通過一些Kernel32 API函式調用訪問。在進程內部創建、並發執行的各個執行緒,可以看作是執行相同動作(代碼是一樣的),但輸入的數據不同,所以輸出的結果數據也不同。因此各個執行緒使用的數據結構是相同的,只是有些變數是被所有的執行緒共享訪問,為進程全局變數;另外一些變數是由每個執行緒獨享訪問,即執行緒局部存儲。而每個執行緒局部存儲的地址需要存入該執行緒的TLS索引數組。

舉例說明:設每個執行緒都要使用執行緒私有的一個浮點型變數fvalue與一個長度為512個位元組的緩衝區buf。需要在啟動這些執行緒前,在主進程中先為fvalue與buf兩個執行緒局部存儲變數在TLS索引數組申請兩個條目,假設為fvalue申請到第3號條目,為buf申請到第5號條目。也就是說,在 任何一個執行緒內訪問該執行緒私有的fvalue,需要查詢該執行緒自己的TLS索引數組,其第3號條目存放的就是fvalue的地址。當然,啟動各個執行緒後還需要為執行緒私有的fvalue與buf從堆中申請到存儲空間,然後把fvalue與buf的地址登記入該執行緒的TLS索引數組的對應的第3號、第5號條目中,之後才能在該執行緒各處使用執行緒私有的fvalue與buf。

第一步,在主進程內調用TlsAlloc()函式,從將要啟動的 每個執行緒的TLS索引數組中預定一個條目(slot),並返回該條目的序號:

DWORD global_dwTLS_fvalue = TLSAlloc();

注意,此步之後,變數( global_dwTLS_fvalue )保存的是分配得到的TLS索引數組的某個條目的序號,例如值為3。編程者在寫這個程式代碼時規定了這個變數( global_dwTLS_fvalue )保存了執行緒局部存儲fvalue在每個執行緒的TLS索引數組的對應條目的序號。變數( global_dwTLS_fvalue )是普通的全局變數,各個執行緒隨後只需要讀取它的值。類似的,另外一個執行緒局部存儲buf變數也需要定義一個變數( global_dwTLS_buf )並用TLSAlloc()初始化。

第二步,在每個進程執行的一開頭,從堆中動態分配一塊記憶體區域(使用LocalAlloc()函式調用)

void* p_fvalue = LocalAlloc(LPTR,sizeof(float));

然後使用TlsSetValue()函式調用,把這塊記憶體區域的地址存入TLS索引數組相應的條目中:

TlsSetValue( global_dwTLS_fvalue, p_fvalue);

第三步,在每個執行緒的任意執行位置,都可以通過該執行緒私有的TLS索引數組的相應條目,使用TlsGetValue()函式得到上一步的那塊記憶體區域的地址,然後就可以對該記憶體區域做讀寫操作了。這就實現了在一個執行緒內部處處可訪問的執行緒局部存儲。

LPVOID lpvData = TlsGetValue(global_dwTLS_fvalue);

*lpvData = (float) 3.1416; //套用該執行緒局部存儲

最後,如果不再需要上述執行緒局部靜態變數,要動態釋放掉這塊記憶體區域(使用LocalFree()函式),這一般線上程即將結束時清理執行緒占用的各項資源時釋放。然後,主進程從TLS索引數組中放棄對應的條目的占用(使用TlsFree()函式)。

LocalFree((HLOCAL) p_fvalue );

TlsFree(global_dwTLS_fvalue);

方法二

直接聲明這個變數是各個執行緒有自己拷貝的執行緒局部靜態變數:

__declspec( thread ) int var_name;

但在Vista與Server 2008之前的作業系統,僅限於在應用程式的主進程(.exe)以及與主進程一起裝入記憶體的動態連線庫(.dll),才能正常裝入本方法所聲明的執行緒靜態存儲。

相關詞條

相關搜尋

熱門詞條

聯絡我們