1. 程式人生 > >UNIX環境高階程式設計——執行緒私有資料

UNIX環境高階程式設計——執行緒私有資料

執行緒私有資料(Thread-specific data,TSD):儲存和查詢與某個執行緒相關資料的一種機制。

1、在程序內的所有執行緒都共享相同的地址空間,即意味著任何宣告為靜態或外部變數,或在程序堆宣告的變數,       都可以被程序內所有的執行緒讀寫。 2、一個執行緒真正擁有的唯一私有儲存是處理器暫存器,棧在“主人”故意暴露給其他執行緒時也是共享的。 3、有時需要提供執行緒私有資料:可以跨多個函式訪問(全域性);僅在某個執行緒有效(私有)(即線上程裡面是全域性)。例如:errno。

程序中的所有執行緒都可以訪問程序的整個地址空間,除非使用暫存器(一個執行緒真正擁有的唯一私有儲存是處理器暫存器),執行緒沒有辦法阻止其它執行緒訪問它的資料,執行緒私有資料也不例外,但是管理執行緒私有資料的函式可以提高執行緒間的資料獨立性。

程序內的所有執行緒共享程序的資料空間,因此全域性變數為所有執行緒所共有。但有時執行緒也需要儲存自己的私有資料,這時可以建立執行緒私有資料(Thread-specific Date)TSD來解決。線上程內部,私有資料可以被各個函式訪問,但對其他執行緒是遮蔽的。例如我們常見的變數errno,它返回標準的出錯資訊。它顯然不能是一個區域性變數,幾乎每個函式都應該可以呼叫它;但它又不能是一個全域性變數。(即線上程裡面是全域性變數)

建立執行緒私有資料就是為了執行緒內部各個函式可以很容易的傳遞資料資訊,因此要使執行緒外的函式不能訪問這些資料,而執行緒內的函式使用這些資料就像執行緒內的全域性變數一樣,這些資料在一個執行緒內部是全域性的,一般用執行緒私有資料的地址作為執行緒內各個函式訪問該資料的入口。

 執行緒私有資料採用了一種被稱為一鍵多值的技術,即一個鍵對應多個數值。訪問資料時都是通過鍵值來訪問,好像是對一個變數進行訪問,其實是在訪問不同的資料。使用執行緒私有資料時,首先要為每個執行緒私有資料建立一個相關聯的鍵。在各個執行緒內部,都使用這個公用的鍵來指代執行緒資料,但是在不同的執行緒中,這個鍵代表的資料是不同的。操作執行緒私有資料的函式主要有4個:pthread_key_create(建立一個鍵),pthread_setspecific(為一個鍵設定執行緒私有資料),pthread_getspecific(從一個鍵讀取執行緒私有資料),pthread_key_delete(刪除一個鍵)。

建立一個鍵:

int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));//返回值:若成功則返回0,否則返回錯誤編號 在分配(malloc)執行緒私有資料之前,需要建立和執行緒私有資料相關聯的鍵(key),這個鍵的功能是獲得對執行緒私有資料的訪問權。
如果建立一個執行緒私有資料鍵,必須保證pthread_key_create對於每個Pthread_key_t變數僅僅被呼叫一次,因為如果一個鍵被建立兩次,其實是在建立兩個不同的鍵,第二個鍵將覆蓋第一個鍵,第一個鍵以及任何執行緒可能為其關聯的執行緒私有資料值將丟失。
建立新鍵時,每個執行緒的私有資料地址設為NULL。

注意:建立的鍵存放在keyp指向的記憶體單元,這個鍵可以被程序中的所有執行緒使用,但每個執行緒把這個鍵與不同的執行緒私有資料地址進行關聯。

除了建立鍵以外,pthread_key_create可以選擇為該鍵關聯解構函式,當執行緒退出時,如果執行緒私有資料地址被置為非NULL值,那麼解構函式就會被呼叫。

注意:解構函式引數為退出執行緒的私有資料的地址。如果私有資料的地址為NULL,就說明沒有解構函式與鍵關聯即不需要呼叫該解構函式。

當執行緒呼叫pthread_exit或者執行緒執行返回,正常退出時,解構函式就會被呼叫,但是如果執行緒呼叫了exit、_exit、Exit函式或者abort或者其它非正常退出時,就不會呼叫解構函式。

執行緒通常使用malloc為執行緒私有資料分配空間,解構函式通常釋放已分配的執行緒私有資料的記憶體。

執行緒可以為執行緒私有資料分配多個鍵,每個鍵都可以有一個解構函式與它關聯。各個鍵的解構函式可以互不相同,當然它們也可以使用相同的解構函式。

執行緒退出時,執行緒私有資料的解構函式將按照作業系統實現定義的順序被呼叫。解構函式可能呼叫另外一個函式,而該函式可能建立新的執行緒私有資料而且把這個執行緒私有資料和當前的鍵關聯起來。當所有的解構函式都呼叫完成以後,系統會檢查是否有非NULL的執行緒私有資料值與鍵關聯,如果有的話,再次呼叫解構函式,這個過程一直重複到執行緒所有的鍵都為NULL值執行緒私有資料,或者已經做了PTHREAD_DESTRUCTOR_ITERATIONS中定義的最大次數的嘗試。

取消鍵與執行緒私有資料之間的關聯:

int pthread_delete(pthread_key_t *keyp);//返回值:若成功則返回0,否則返回錯誤編號 注意呼叫pthread_delete不會啟用與鍵關聯的解構函式。刪除執行緒私有資料鍵的時候,不會影響任何執行緒對該鍵設定的執行緒私有資料值,甚至不影響呼叫執行緒當前鍵值,所以容易造成記憶體洩露(因為鍵不與私有資料關聯了,當執行緒正常退出的時候不會呼叫鍵的解構函式,最終導致執行緒的私有資料這塊記憶體沒有釋放)。使用已經刪除的私有資料鍵將導致未定義的行為。

 注意:對於每個pthread_key_t變數(即鍵)必須僅呼叫一次pthread_key_create。如果一個鍵建立兩次,其實是在建立不同的鍵,第二個鍵將覆蓋第一個,第一個鍵與任何執行緒可能為其設定的值將一起永遠的丟失。所以,pthread_key_create放在主函式中執行;或每個執行緒使用pthread_once來建立鍵。

執行緒私有資料與鍵關聯:

int pthread_setspecific(pthread_key_t key,const void *value);//返回值:若成功則返回0,否則返回錯誤編號
void* pthread_getspecific(pthread_key_t key);//返回值:執行緒私有資料地址;若沒有值與鍵關聯則返回NULL
如果沒有執行緒私有資料值與鍵關聯,pthread_getspecific鍵返回NULL,可以依據此來確定是否呼叫pthread_setspecific。

注意:兩個執行緒對自己的私有資料操作是互相不影響的。也就是說,雖然 key 是同名且全域性,但訪問的記憶體空間並不是相同的一個。key 就像是一個數據管理員,執行緒的私有資料只是到他那去註冊,讓它知道你這個資料的存在
示例程式碼: #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

typedef struct private_tag {
    pthread_t   thread_id;
    char        *string;
} private_t;

pthread_key_t identity_key;         /* Thread-specific data key */
pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER;
long identity_key_counter = 0;


void identity_key_destructor (void *value)
{
    private_t *private = (private_t*)value;
    int status;

    printf ("thread \"%s\" exiting...\n", private->string);
    free (value);
    status = pthread_mutex_lock (&identity_key_mutex);
    if (status != 0)
        perror("pthread_mutex_lock");
    identity_key_counter--;
    if (identity_key_counter <= 0) {
        status = pthread_key_delete (identity_key);
        if (status != 0)
            perror("pthread_key_delete");
        printf ("key deleted...\n");
    }
    status = pthread_mutex_unlock (&identity_key_mutex);
    if (status != 0)
        perror("pthread_mutex_unlock");
}

void *identity_key_get (void)
{
    void *value;
    int status;

    value = pthread_getspecific (identity_key);
    if (value == NULL) {
        value = malloc (sizeof (private_t));
        if (value == NULL)
            perror ("malloc");
        status = pthread_setspecific (identity_key, (void*)value);
        if (status != 0)
            perror("pthread_setspecific");
    }
    return value;
}

void *thread_routine (void *arg)
{
    private_t *value;

    value = (private_t*)identity_key_get ();
    value->thread_id = pthread_self ();
    value->string = (char*)arg;
    printf ("thread \"%s\" starting...\n", value->string);
    sleep (2);
    return NULL;   
}

void main (int argc, char *argv[])
{
    pthread_t thread_1, thread_2;
    private_t *value;
    int status;

    status = pthread_key_create (&identity_key, identity_key_destructor);
    if (status != 0)
        perror("pthread_key_create");
    identity_key_counter = 3;
    value = (private_t*)identity_key_get ();
    value->thread_id = pthread_self ();
    value->string = "Main thread";
    status = pthread_create (&thread_1, NULL,thread_routine, "Thread 1");
    if (status != 0)
        perror("pthread_create");
    status = pthread_create (&thread_2, NULL,thread_routine, "Thread 2");
    if (status != 0)
        perror("pthread_create");
    pthread_exit (NULL);
}
執行結果:
[email protected]:~$ ./a.out
thread "Main thread" exiting...
thread "Thread 2" starting...
thread "Thread 1" starting...
thread "Thread 2" exiting...
thread "Thread 1" exiting...
key deleted...
[email protected]:~$

示例程式碼2:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

typedef struct tsd_tag{
        pthread_t thread_id;
        char *string;
}tsd_t;

pthread_key_t key;
pthread_once_t once = PTHREAD_ONCE_INIT;

void once_routine(void)
{
        int status;

        printf("Initializing key\n");
        status = pthread_key_create(&key, NULL);
        if(status != 0){
           perror("pthread_key_create");
        }
}

void *thread_routine(void *arg)
{
        int status;
        tsd_t *value = NULL;

        status = pthread_once(&once, once_routine);
        if(status != 0){
            perror("pthread_once");
        }

        value = (tsd_t *)malloc(sizeof(tsd_t));
        if(value == NULL){
            perror("malloc");
        }

        status = pthread_setspecific(key, (void *)value);
        if(status != 0){
           perror("pthread_setspecific");
        }

        printf("%s set tsd value at %p\n", (char *)arg, value);
        value->thread_id = pthread_self();
        value->string = (char *)arg;

        printf("%s starting......\n", (char *)arg);
        sleep(2);
        value = (tsd_t *)pthread_getspecific(key);
        if(value == NULL){
            printf("no thread-specific data value was associated \
                        with key\n"
);
            pthread_exit(NULL);
        }
        printf("%s done......\n", value->string);
}

int main(int argc, char **argv)
{
        pthread_t thread1, thread2;
        int status;

        status = pthread_create(&thread1, NULL, thread_routine, "thread 1");
        if(status != 0){
                perror("pthread_create");
        }

        status = pthread_create(&thread2, NULL, thread_routine, "thread 2");
        if(status != 0){
                perror("pthread_create");
        }

        pthread_exit(NULL);
}

執行結果:
[email protected]:~$ ./a.out
Initializing key
thread 2 set tsd value at 0x8fb7520
thread 2 starting......
thread 1 set tsd value at 0x8fb7530
thread 1 starting......
thread 2 done......
thread 1 done......

示例程式碼3:

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

執行結果:
[email protected]:~$ ./a.out  
結構體struct_data的地址為 0x0xb77db388  
child1 中 pthread_getspecific(key)返回的指標為:0x0xb77db388  
利用 pthread_getspecific(key)列印 child1 執行緒中與key關聯的結構體中成員值:  
struct_data.i:10  
struct_data.k: 3.141500  
------------------------------------------------------  
child2 中變數 temp 的地址為 0x0xb6fda38c  
child2 中 pthread_getspecific(key)返回的指標為:0x0xb6fda38c  
利用 pthread_getspecific(key)列印 child2 執行緒中與key關聯的整型變數temp 值:20  

相關推薦

UNIX環境高階程式設計——執行私有資料

執行緒私有資料(Thread-specific data,TSD):儲存和查詢與某個執行緒相關資料的一種機制。 1、在程序內的所有執行緒都共享相同的地址空間,即意味著任何宣告為靜態或外部變數,或在程序堆宣告的變數,       都可以被程序內所有的執行緒讀寫。 2、一個

Linux系統程式設計——執行私有資料

在多執行緒程式中,經常要用全域性變數來實現多個函式間的資料共享。由於資料空間是共享的,因此全域性變數也為所有執行緒共有。 測試程式碼如下: #include <stdio.h> #include <pthread.h> #include <unis

UNIX環境高階程式設計》(APUE) 筆記第十一章 - 執行

# 11 - 執行緒 [Github 地址](https://github.com/XutongLi/Learning-Notes/blob/master/APUE/11-%E7%BA%BF%E7%A8%8B.md) *** ## 1. 執行緒概念 典型的 **UNIX程序** 可以看成只有一個 **控制

Unix環境高階程式設計入門----Unix環境及系統資料資訊使用

                                         Unix環境高階程式設計入門                                                   ----Unix環境及系統資料資訊使用          Uni

執行apue (unix 環境高階程式設計)第一個例子

拿到APUE第三版,大家肯定都是想驗證書上的程式碼,用除錯程式碼的過程來學習本書。但是Linux不像Windows,linux是一個free(自由)軟體,free軟體帶來的問題就是執行一個小小的程式都要折騰半天。現在把執行第一個程式的過程記錄下來,以供後來查閱。 第一步,當

UNIX環境高階程式設計》 —— 書中例項編譯、執行

想認真學習《UNIX環境高階程式設計》的人士可能都想把書中的例項給敲一遍,執行看一下效果。但對於新手來說,可能會有一定的困難,因為例子中用到的標頭檔案和一些函式不知道在哪裡,編譯可能也會出問題。這邊文章就是以一個例項來介紹如何執行這本書中的例項。 但這樣還不行,如果直接編

linux下執行UNIX環境高階程式設計》的第一個程式時原始碼編譯出錯的處理方法

前幾天買了《UNIX環境高階程式設計》這本書,想好好學習下linux的程式設計。誰知道看到第一個列出指定目錄的內容的那個例子,其實就是shell中 ls 的內容,打好程式碼要執行時一直出問題。後來在網上找了挺多的解決方法,終於解決了。先把方法貼上。 先在終端裡面輸入 vi

UNIX環境高階程式設計》 第6章 系統資料檔案和資訊

系統資料檔案和資訊 6.1 引言 UNIX系統的正常運作需要使用大量的與系統有關的資料檔案,例如口令檔案/etc/passwd和組檔案/etc/group就是經常被多個程式頻繁使用的兩個檔案。每次使用者登入系統,以及每次執行ls -l命令都需要使用口

【多執行程式設計執行私有資料(TSD)

Thread Specific Data(TSD) 執行緒私有資料,有什麼用呢?在多執行緒中,經常要用全域性變數來實現多個函式間的資料共享。由於資料空間是共享的,因此全域性變數也為所有程序共有。但有時應用程式設計中必要提供執行緒私有的全域性變數,這個變數被各個執行緒私有,但

【C/C++多執行程式設計之十】pthread執行私有資料

#include #include #include #include #pragma comment(lib, "pthreadVC2.lib") //必須加上這句 pthread_key_t key; pthread_mutex_t mutex; pthread_t tid1,*p1;

UNIX環境高階程式設計的學習(二)

UNIX系統程序控制程式說明 該程式從標準輸入讀取命令,然後執行這些命令,程式涉及到的函式主要用法包括: fgets的用法 execlp的用法 waitpid的用法 程式如下: #include "apue.h" #include <sys/wai

UNIX環境高階程式設計的學習(一)

UNIX環境高階程式設計第一個例子的編譯 這本書中有很多的例子,為了加深理解,習慣性自己敲一遍程式碼然後看執行結果,再去理解其中的知識點,但是在虛擬機器下如何編譯這些程式碼呢,需要以下幾步: 下載並解壓縮apue.3e檔案包 在“apue.h”中最後一行加:#inclu

[UNIX環境高階程式設計] 檔案和目錄

1 引言 上文圍繞了普通檔案I/O進行了討論——開啟檔案、讀檔案或寫檔案。本文將描述檔案系統的其他特徵和檔案的性質。將從stat函式開始,stat結構中的大多數成員都是基本系統資料型別,逐個分解stat結構的每一個成員以瞭解檔案的所有屬性。 使用stat函式最多的地方可能就是[ls -l

[Unix環境高階程式設計] 檔案I/O

1.引言 UNIX系統中的大多數檔案I/O只需要用到5個函式:open、read、write、lseek以及close,這裡所涉及到的函式經常被稱為不帶緩衝的I/O1。只要涉及在多個程序之間共享資源,原子操作的概念就變得非常重要,我們將通過I/O和open函式的引數來討論此概念,dup、

UNIX環境高階程式設計(第三版) 第五章筆記

5.2 流和物件 只有兩個函式可以改變流的定向: freopen函式清楚一個流的定向,fwide函式可用於設定一個流的定向。 #include <stdio.h> #include <wchar.h> int fwide(FILE

UNIX環境高階程式設計(三) 第六章

6 系統資料檔案和資訊 6.2 口令檔案 #include <pwd.h> struct passwd *getpwuid(uid_t uid); /* 檢視使用者登入名 */ struct passwd *getpwnam(const

執行私有資料

    在參考ptmalloc實現的時候,碰到了一個新東西——執行緒私有資料,其資料是這樣宣告的 static tsd_key_t arena_key;。由於ptmalloc中的許多資料結構都是在內部重新定義的,而我手中的這部分原始碼並不是完全的,所以我只好在網上查詢tsd_ke

UNIX環境高階程式設計(3) 第三章

3 檔案I/O 3.1 引言 3.2 檔案描述符 檔案描述符是一個標示,非負整數,類似於windows裡的控制代碼,為了與標準C保持一致(標準C裡的檔案的讀寫都是通過File Pointer)UNIX採用了這樣的三級結構,我混淆於檔案描述標誌和檔案

UNIX環境高階程式設計(3) 第八章

8 程序控制 8.1 引言 8.2 程序標識 #include <unistd.h> pid_t getpid(void); return: 呼叫程序的程序ID pid_t getppid(void

UNIX環境高階程式設計(3) 第二章

2.2UNIX標準化 2.2.1 ISO C 國際標準化組織(International Organization for Standardization,ISO) 國際電子技術委員會(International Electrotechnical Co