為什麼要有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()函數)。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_key_t key;
struct test_struct {
int i;
float k;
};
void *child1 (void *arg)
{
struct test_struct struct_data;
struct_data.i = 10;
struct_data.k = 3.1415;
pthread_setspecific (key, &struct_data);
printf ("結構體struct_data的位址為 0x%p\n", &(struct_data));
printf ("child1 中 pthread_getspecific(key)傳回的指針為:0x%p\n", (struct test_struct *)pthread_getspecific(key));
printf ("利用 pthread_getspecific(key)列印 child1 線程中與key關聯的結構體中成員值:\nstruct_data.i:%d\nstruct_data.k: %f\n", ((struct test_struct *)pthread_getspecific (key))->i, ((struct test_struct *)pthread_getspecific(key))->k);
printf ("------------------------------------------------------\n");
}
void *child2 (void *arg)
int temp = 20;
sleep (2);
printf ("child2 中變量 temp 的位址為 0x%p\n", &temp);
pthread_setspecific (key, &temp);
printf ("child2 中 pthread_getspecific(key)傳回的指針為:0x%p\n", (int *)pthread_getspecific(key));
printf ("利用 pthread_getspecific(key)列印 child2 線程中與key關聯的整型變量temp 值:%d\n", *((int *)pthread_getspecific(key)));
int main (void)
pthread_t tid1, tid2;
pthread_key_create (&key, NULL);
pthread_create (&tid1, NULL, (void *)child1, NULL);
pthread_create (&tid2, NULL, (void *)child2, NULL);
pthread_join (tid1, NULL);
pthread_join (tid2, NULL);
pthread_key_delete (key);
return (0);
./pthread_key
結構體struct_data的位址為 0x0xb7699388
child1 中 pthread_getspecific(key)傳回的指針為:0x0xb7699388
利用 pthread_getspecific(key)列印 child1 線程中與key關聯的結構體中成員值:
struct_data.i:10
struct_data.k: 3.141500
------------------------------------------------------
child2 中變量 temp 的位址為 0x0xb6e9838c
child2 中 pthread_getspecific(key)傳回的指針為:0x0xb6e9838c