複製鏈接
請複製以下鏈接發送給好友

線程局部存儲

鎖定
英文為Thread Local Storage,縮寫為TLS。為什麼要有TLS?原因在於,全局變量與函數內定義的靜態變量,是各個線程都可以訪問的共享變量
中文名
線程局部存儲
外文名
Thread Local Storage
所屬學科
計算機

目錄

線程局部存儲信息介紹

線程局部存儲 線程局部存儲
在一個線程修改的內存內容,對所有線程都生效。這是一個優點也是一個缺點。説它是優點,線程的數據交換變得非常快捷。説它是缺點,一個線程死掉了,其它線程也性命不保; 多個線程訪問共享數據,需要昂貴的同步開銷,也容易造成同步相關的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;
但在VistaServer 2008之前的操作系統,僅限於在應用程序的主進程(.exe)以及與主進程一起裝入內存的動態連接庫(.dll),才能正常裝入本方法所聲明的線程靜態存儲。