天天看點

線程局部存儲(ACE_TSS的内部實作)

為什麼要有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