1. 程式人生 > >執行緒私有資料STD

執行緒私有資料STD

概念及作用

        在單執行緒程式中,我們經常要用到"全域性變數"以實現多個函式間共享資料。在多執行緒環境下,由於資料空間是共享的,因此全域性變數也為所有執行緒所共有。但有時應用程式設計中有必要提供執行緒私有的全域性變數僅在某個執行緒中有效,但卻可以跨多個函式訪問,比如程式可能需要每個執行緒維護一個連結串列,而使用相同的函式操作,最簡單的辦法就是使用同名而不同變數地址的執行緒相關資料結構。這樣的資料結構可以由Posix執行緒庫維護,稱為執行緒私有資料(Thread-specificData,或TSD)

為什麼要執行緒私有資料?

        原因一:有時候需要維護基於每個執行緒的資料

,用執行緒ID作為索引。因為執行緒ID不能保證是小而連續的整數,所以不能簡單的分配一個執行緒資料陣列,用執行緒ID作為陣列的索引。即使執行緒ID確實是小而連續的整數,可能還希望有一些額外的保護,以防止某個執行緒的資料和其它執行緒的資料相混淆。
原因二:可以讓基於程序的介面適應多執行緒環境,比如errno,執行緒出現以前errno被定義成程序環境中全域性可訪問的整數,執行緒出現以後,為了讓執行緒也能使用那些原本基於程序的系統呼叫和庫例程,errno被重新定義成執行緒私有資料,這樣,一個執行緒做了設定errno的操作並不會影響到其他執行緒的errno值。
(參考APUE)

         程序中的所有執行緒都可以訪問程序的整個地址空間,除非使用暫存器

(一個執行緒真正擁有的唯一私有儲存是處理器暫存器),執行緒沒有辦法阻止其它執行緒訪問它的資料,執行緒私有資料也不例外,但是管理執行緒私有資料的函式可以提高執行緒間的資料獨立性。

建立和登出

Posix定義了兩個API分別用來建立和登出TSD:

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))

該函式從TSD池中分配一項,將其值賦給key供以後訪問使用。如果destr_function不為空,線上程退出(pthread_exit())時將以key所關聯的資料為引數呼叫destr_function(),以釋放分配的緩衝區。

不論哪個執行緒呼叫pthread_key_create(),所建立的key都是所有執行緒可訪問的,但各個執行緒可根據自己的需要往key中填入不同的值,這就相當於提供了一個同名而不同值的全域性變數。在LinuxThreads的實現中,TSD池用一個結構陣列表示:

static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };

建立一個TSD就相當於將結構陣列中的某一項設定為"in_use",並將其索引返回給*key,然後設定destructor函式為destr_function。

登出一個TSD採用如下API:

int pthread_key_delete(pthread_key_t key)

這個函式並不檢查當前是否有執行緒正使用該TSD,也不會呼叫清理函式(destr_function),而只是將TSD釋放以供下一次呼叫pthread_key_create()使用。在LinuxThreads中,它還會將與之相關的執行緒資料項設為NULL(見"訪問")。

訪問

TSD的讀寫都通過專門的Posix Thread函式進行,其API定義如下:

int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)
void * pthread_getspecific(pthread_key_t key)

寫入(pthread_setspecific())時,將pointer的值(不是所指的內容)與key相關聯,而相應的讀出函式則將與key相關聯的資料讀出來。資料型別都設為void*,因此可以指向任何型別的資料。

在LinuxThreads中,使用了一個位於執行緒描述結構(_pthread_descr_struct)中的二維void*指標陣列來存放與key關聯的資料,陣列大小由以下幾個巨集來說明:

#define PTHREAD_KEY_2NDLEVEL_SIZE       32
#define PTHREAD_KEY_1STLEVEL_SIZE   \
((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)
/ PTHREAD_KEY_2NDLEVEL_SIZE)
    其中在/usr/include/bits/local_lim.h中定義了PTHREAD_KEYS_MAX為1024,
    因此一維陣列大小為32。而具體存放的位置由key值經過以下計算得到:
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE

也就是說,資料存放與一個32×32的稀疏矩陣中。同樣,訪問的時候也由key值經過類似計算得到資料所在位置索引,再取出其中內容返回。

使用範例

以下這個例子沒有什麼實際意義,只是說明如何使用,以及能夠使用這一機制達到儲存執行緒私有資料的目的。

#include <stdio.h>
#include <pthread.h>
pthread_key_t   key;
void echomsg(int t)
{
        printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);
}
void * child1(void *arg)
{
        int tid=pthread_self();
        printf("thread %d enter\n",tid);
        pthread_setspecific(key,(void *)tid);
        sleep(2);
        printf("thread %d returns %d\n",tid,pthread_getspecific(key));
        sleep(5);
}
void * child2(void *arg)
{
        int tid=pthread_self();
        printf("thread %d enter\n",tid);
        pthread_setspecific(key,(void *)tid);
        sleep(1);
        printf("thread %d returns %d\n",tid,pthread_getspecific(key));
        sleep(5);
}
int main(void)
{
        int tid1,tid2;
        printf("hello\n");
        pthread_key_create(&key,echomsg);
        pthread_create(&tid1,NULL,child1,NULL);
        pthread_create(&tid2,NULL,child2,NULL);
        sleep(10);
        pthread_key_delete(key);
        printf("main thread exit\n");
        return 0;
}

給例程建立兩個執行緒分別設定同一個執行緒私有資料為自己的執行緒ID,為了檢驗其私有性,程式錯開了兩個執行緒私有資料的寫入和讀出的時間,從程式執行結果可以看出,兩個執行緒對TSD的修改互不干擾。同時,當執行緒退出時,清理函式會自動執行,引數為tid。