為什麼要有TLS?原因在於,進程中的全局變數與函式內定義的靜態(static)變數,是各個執行緒都可以訪問的共享變數。在一個執行緒修改的記憶體內容,對所有執行緒都生效。這是一個優點也是一個缺點。說它是優點,執行緒的數據交換變得非常快捷。說它是缺點,一個執行緒死掉了,其它執行緒也性命不保; 多個執行緒訪問共享數據,需要昂貴的同步開銷,也容易造成同步相關的BUG。
如果需要在一個執行緒內部的各個函式調用都能訪問、但其它執行緒不能訪問的變數(被稱為static memory local to a thread 執行緒局部靜態變數),就需要新的機制來實現。這就是TLS。
執行緒局部存儲在不同的平台有不同的實現,可移植性不太好。幸好要實現執行緒局部存儲並不難,最簡單的辦法就是建立一個全局表,通過當前執行緒ID去查詢相應的數據,因為各個執行緒的ID不同,查到的數據自然也不同了。
大多數平台都提供了執行緒局部存儲的方法,無需要我們自己去實現:
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
方法一:每個執行緒創建時系統給它分配一個LPVOID指針的數組(叫做TLS數組),這個數組從C編程角度是隱藏著的不能直接訪問,需要通過一些C API函式調用訪問。首先定義一些DWORD執行緒全局變數或函式靜態變數,準備作為各個執行緒訪問自己的TLS數組的索引變數。一個執行緒使用TLS時,第一步線上程內調用TlsAlloc()函式,為一個TLS數組索引變數與這個執行緒的TLS數組的某個槽(slot)關聯起來,例如獲得一個索引變數:
global_dwTLSindex=TLSAlloc();
注意,此步之後,當前執行緒實際上訪問的是這個TLS數組索引變數的執行緒內的拷貝版本。也就說,不同執行緒雖然看起來用的是同名的TLS數組索引變數,但實際上各個執行緒得到的可能是不同DWORD值。其意義在於,每個使用TLS的執行緒獲得了一個DWORD類型的執行緒局部靜態變數作為TLS數組的索引變數。C/C++原本沒有直接定義執行緒局部靜態變數的機制,所以在如此大費周折。
第二步,為當前執行緒動態分配一塊記憶體區域(使用LocalAlloc()函式調用),然後把指向這塊記憶體區域的指針放入TLS數組相應的槽中(使用TlsValue()函式調用)。
第三步,在當前執行緒的任何函式內,都可以通過TLS數組的索引變數,使用TlsGetValue()函式得到上一步的那塊記憶體區域的指針,然後就可以進行記憶體區域的讀寫操作了。這就實現了在一個執行緒內部這個範圍處處可訪問的變數。
最後,如果不再需要上述執行緒局部靜態變數,要動態釋放掉這塊記憶體區域(使用LocalFree()函式),然後從TLS數組中放棄對應的槽(使用TlsFree()函式)。