TLS技術資料

縮寫爲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)關聯起來,例如獲得一個索引變量:


   DWORD global_dwTLS_fvalue = TLSAlloc();


  注意,此步之後,當前線程實際上訪問的是這個TLS數組索引變量的線程內的拷貝版本。也就說,不同線程雖然看起來用的是同名的TLS數組索引變量,但實際上各個線程得到的可能是不同DWORD值。其意義在於,每個使用TLS的線程獲得了一個DWORD類型的線程局部靜態變量作爲TLS數組的索引變量。C/C++原本沒有直接定義線程局部靜態變量的機制,所以在如此大費周折。


  第二步,爲當前線程動態分配一塊內存區域(使用LocalAlloc()函數調用),然後把指向這塊內存區域的指針放入TLS數組相應的槽中(使用TlsValue()函數調用)。

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

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);


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章