1. 程式人生 > >Linux多執行緒實踐(四 )執行緒的特定資料

Linux多執行緒實踐(四 )執行緒的特定資料

在單執行緒程式中,我們經常要用到"全域性變數"以實現多個函式間共享資料, 然而在多執行緒環境下,由於資料空間是共享的,因此全域性變數也為所有執行緒所共有。但有時應用程式設計中有必要提供執行緒私有的全域性變數,僅在某個執行緒中有效,但卻可以跨多個函式訪問。POSIX執行緒庫通過維護一定的資料結構來解決這個問題,這個些資料稱為(Thread-specific-data或 TSD)。

相關函式如下:

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));  
int pthread_key_delete(pthread_key_t key);  
  
int pthread_setspecific(pthread_key_t key, const void *pointer);  
void * pthread_getspecific(pthread_key_t key);  
  
pthread_once_t once_control = PTHREAD_ONCE_INIT;  
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));  

  從上圖可知:當呼叫pthread_key_create 後會產生一個所有執行緒都可見的執行緒特定資料(TSD)的鍵值(如上圖中所有的執行緒都會得到一個pkey[1]的值), 但是這個鍵所指向的真實資料卻是不同的,雖然都是pkey[1], 但是他們並不是指向同一塊記憶體,而是指向了只屬於自己的實際資料, 因此, 如果執行緒0更改了pkey[1]所指向的資料, 而並不能夠影像到執行緒n;

   線上程呼叫pthread_setspecific後會將每個執行緒的特定資料與thread_key_t繫結起來,雖然只有一個pthread_key_t,但每個執行緒的特定資料是獨立的記憶體空間,當執行緒退出時會執行destructor 函式。

/** 示例1:運用pthread_once, 讓key只初始化一次 
注意: 將對key的初始化放入到init_routine中 
**/  
pthread_key_t key;  
pthread_once_t once_control = PTHREAD_ONCE_INIT;  
typedef struct Tsd  
{  
    pthread_t tid;  
    char *str;  
} tsd_t;  
  
//執行緒特定資料銷燬函式,  
//用來銷燬每個執行緒所指向的實際資料  
void destructor_function(void *value)  
{  
    free(value);  
    cout << "destructor ..." << endl;  
}  
  
//初始化函式, 將對key的初始化放入該函式中,  
//可以保證inti_routine函式只執行一次  
void init_routine()  
{  
    pthread_key_create(&key, destructor_function);  
    cout << "init..." << endl;  
}  
  
void *thread_routine(void *args)  
{  
    pthread_once(&once_control, init_routine);  
  
    //設定執行緒特定資料  
    tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t));  
    value->tid = pthread_self();  
    value->str = (char *)args;  
    pthread_setspecific(key, value);  
    printf("%s setspecific, address: %p\n", (char *)args, value);  
  
    //獲取執行緒特定資料  
    value = (tsd_t *)pthread_getspecific(key);  
    printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);  
    sleep(2);  
  
    //再次獲取執行緒特定資料  
    value = (tsd_t *)pthread_getspecific(key);  
    printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);  
  
    pthread_exit(NULL);  
}  
  
int main()  
{  
    pthread_t tid1, tid2;  
    pthread_create(&tid1, NULL, thread_routine, (void *)"thread1");  
    pthread_create(&tid2, NULL, thread_routine, (void *)"thread2");  
  
    pthread_join(tid1, NULL);  
    pthread_join(tid2, NULL);  
    pthread_key_delete(key);  
  
    return 0;  
}  
執行結果如下:

init....
thread1 setspecific ,address: 0x7fe7a00008c0
tid: 0xa8192700, str = thread1
thread2 setspecific ,address :0x7fe7980008c0
tid: 0xa7991700 ,str = thread2
tid: 0xa8192700 ,str = thread1
tid: 0xa7001700 ,str = thread2
destructor...
destructor...


主執行緒建立了兩個執行緒然後join 等待他們退出;給每個執行緒的執行函式都是thread_routine,thread_routine 中呼叫了pthread_once,此函式表示如果當第一個執行緒呼叫它時會執行once_routine,然後從once_routine返回即pthread_once 返回,而接下去的其他執行緒呼叫它時將不再執行once_routine,此舉是為了只調用pthread_key_create 一次,即產生一個pthread_key_t 值。

在thread_routine 函式中自定義了執行緒特定資料的型別,對於不同的執行緒來說TSD的內容不同,假設執行緒1在第一次列印完進入睡眠的時候,執行緒2也開始執行並呼叫pthread_setspecific 繫結執行緒2的TSD 和key_t,此時執行緒1呼叫pthread_getspecific 返回key_t 繫結的TSD指標,仍然是執行緒1的TSD指標,即雖然key_t 只有一個,但每個執行緒都有自己的TSD。

特定資料;具有128項,通過key-value實現,一個執行緒建立一個key,其他執行緒也會建立,但是並不是指向的同一快記憶體,他們指向自己的資料,

這就是執行緒特定資料。

上述程式碼中,即使是Sleeep(2),執行緒1的資料並不會被執行緒2的資料所影響,因為是執行緒私有的。

當執行緒退出的時候會銷燬2次,因為建立了兩個執行緒。


其中tid 是執行緒的id,str 是傳遞給thread_routine 的引數,可以看到有兩個不同的ptr,且destroy 呼叫兩次。

另外,關於Linux/Unix執行緒私有資料實現思想:

請參考 http://blog.csdn.net/caigen1988/article/details/7901248,寫的很好。