C語言執行緒物件和執行緒儲存

2020-07-16 10:04:27
當每個執行緒為各自的變數使用全域性識別符號時,為保留這些變數各自的資料,可以採用執行緒物件(thread-local object)和執行緒儲存(thread-specific storage)。

這兩項技術允許在一個給定執行緒中執行的函數可以共用資料而不造成衝突,即便當其他執行緒也在執行同樣函數的情況下。

使用執行緒物件

執行緒物件是在宣告中包含新儲存類修飾符 _Thread_local 的全域性或靜態物件。這意味著:每一個執行緒擁有屬於自己的執行緒物件範例,它線上程啟動時建立並初始化。物件的儲存週期等於執行緒的執行時間。在一個執行緒內表示式裡面的執行緒物件名,將參照這個物件在當前執行緒下的本地範例。

修飾符 _Thread_local 可以與修飾符 static 或 extern 同時使用。標頭檔案 threads.h 定義了 thread_local 作為 _Thread_local 的同義詞。在例 1 中,主執行緒和新啟動執行緒各自擁有執行緒本地變數 var 的一個範例。

【例1】使用一個執行緒物件
#include <stdio.h>
#include <threads.h>

thread_local int var = 10;

void print_var(void){ printf("var = %dn", var); }
int func(void *);               // 執行緒函數

int main(int argc, char *argv[])
{
    thrd_t th1;
    if ( thrd_create( &th1, func, NULL ) != thrd_success ){
      fprintf(stderr,"Error creating thread.n"); return 0xff;
    }
    print_var();                // 輸出:var = 10
    thrd_join(th1, NULL);
    return 0;
}

int func(void *arg)             // 執行緒函數
{
    var += 10;                  // 執行緒本地變數
    print_var();                // 輸出:var = 20
    return 0
}

使用執行緒儲存

執行緒儲存技術要比執行緒物件更加靈活。例如,獨立執行緒可以使用不同大小的記憶體。它們可以動態地分配記憶體,並通過呼叫解構函式再次釋放記憶體。同時,可以使用相同的識別符號存取這些獨立執行緒所在的不同記憶體區域。

這種靈活性通過初始建立一個全域性的鍵(key)實現,該鍵表示了一個指向執行緒儲存的指標。然後,獨立執行緒通過指定其執行緒儲存的位置載入這個指標。該全域性鍵值是型別為 tss_t 的物件。標頭檔案 threads.h 包含了該型別的定義以及 4 個用於管理執行緒儲存(簡稱 TSS)函數的宣告:

int tss_create(tss_t*key,tss_dtor_t dtor);
通過解構函式 dtor 生成一個新的 TSS 指標,並且將 key 參照的物件設定為唯一標識該 TSS 指標的值。型別 tss_dtor_t 是一個函數指標,定義為 void(*)(void*)(它指的是一個函數指標,該函數引數為 void 指標,並且該函數沒有返回值)。dtor 的返回值可以是一個空指標。

void tss_delete(tss_t key);
釋放 TSS 鍵 key 所使用的所有資源。

int tss_set(tss_t key,void*val);
對於呼叫 tss_set()的執行緒,將 key 所標識的 TSS 指標設定為 val 所參照的記憶體地址。

void*tss_get(tss_t key);
返回指向記憶體塊的指標,該記憶體塊為正在呼叫的執行緒通過函數 tss_set()設定。如果發生錯誤,tss_get()返回 NULL。

如果函數 tss_create()和 tss_set()發生錯誤,則返回 thrd_error;否則,返回 thrd_success。

例 2 中的程式在動態分配的執行緒儲存中,保留執行緒的名稱。

【例2】使用執行緒儲存
#include <threads.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

tss_t key;                              // 用於TSS指標的全域性鍵

int thFunc(void *arg);          // 執行緒函數
void destructor(void *data);    // 解構函式

int main(void)
{
    thrd_t th1, th2;
    int result1 = 0, result2 = 0;
    // 建立一個TSS金鑰
    if (tss_create(&key, destructor) != thrd_success)
      return -1;
    // 建立執行緒
    if (thrd_create(&th1, thFunc, "Thread_1") != thrd_success
         || thrd_create(&th2, thFunc, "Thread_2") != thrd_success)
        return -2;

    thrd_join(th1, &result1); thrd_join(th2, &result2);
    if ( result1 != 0 || result2 != 0 )
       fputs("Thread errorn", stderr);
    else
       puts("Threads finished without error.");

    tss_delete(key);            // 釋放TSS指標所有的資源
    return 0;
}

void print(void)                // 顯示執行緒儲存
{
  printf( "print: %sn", (char*)tss_get(key) );
}

int thFunc( void *arg )
{
   char *name = (char*)arg;
   size_t size = strlen(name)+1;

   // 設定執行緒儲存
   if ( tss_set(key, malloc(size)) != thrd_success )
      return -1;
   // 儲存資料
   strcpy((char*)tss_get(key), name);
   print();
   return 0;
}

void destructor(void *data)
{
  printf("Destructor for %sn", (char*)data);
  free(data);           // 釋放記憶體
}