1. 程式人生 > 其它 >linux執行緒特有資料(key)與執行緒區域性儲存(__thread)

linux執行緒特有資料(key)與執行緒區域性儲存(__thread)

技術標籤:初學linux執行緒特有資料 key區域性儲存__thread

執行緒特有資料(key)與執行緒區域性儲存(__thread)

要了解執行緒特有資料區域性儲存的特性。首先需要了解什麼是執行緒安全函式,什麼是非執行緒安全函式

  1. 執行緒安全函式:函式可同時供多個執行緒安全呼叫.即可重入,函式重入時不會造成函式的邏輯混亂。
  2. 非執行緒安全函式:如果不是執行緒安全函式,那麼他就是非執行緒安全函式。導致非安全的主要原因是函式使用了全域性變數,記憶體分配等,在多執行緒並行訪問此函式時,會造成此函式的邏輯錯誤。

將一個非執行緒安全函式轉換為執行緒安全函式的方法很多,如:

  1. 使用互斥量。對函式中的共享資源進行保護,保證函式邏輯的正確性。
    1. 缺點:線上程遇到互斥量時,並行訪問就轉換成了順序訪問。
  2. 避免使用全域性和靜態變數等會造成函式不可重入的機制。
  3. 使用執行緒特有資料與區域性儲存。一般用於庫函式的編寫中。

一般庫函式的編寫中,可能會用到執行緒特有資料與區域性儲存。

執行緒特有資料

執行緒特有資料:簡單的說,就是為每個呼叫執行緒分別維護一份變數的副本。每個執行緒通過特有資料鍵訪問時,這個特有資料鍵都會獲取到本執行緒繫結的變數副本。

執行緒特有資料的使用:

  1. 首先通過pthread_key_create()函式建立初始化一個特有資料鍵,並繫結解構函式(執行緒在中止時呼叫,一般用於記憶體釋放等)。
  2. 然後通過pthread_getspecific()繫結鍵與變數實體。
  3. 最後通過pthread_getspecific()函式獲取變數實體並訪問。

通過特有資料鍵來獲取與訪問。特有資料鍵是全域性的。

  1. int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
    1. 建立一個特有資料鍵。用於之後繫結特有資料。
    2. key:特有資料鍵實體指標。
    3. destructor:解構函式。執行緒終止時呼叫。
    4. return:
  2. int pthread_setspecific(pthread_key_t key, const void *value)
    1. 繫結特有資料鍵與資料實體
    2. key:特有資料鍵
    3. value:資料內容實體。
  3. void *pthread_getspecific(pthread_key_t key)
    1. 獲取指定鍵對應的資料內容。如果未繫結資料實體。則返回NULL。可以利用這一點來判斷自身是否是初次為某個執行緒所呼叫,若為初次,
      則必須為該執行緒分配空間。
    2. 返回資料實體指標。如果未繫結資料實體。則返回NULL。

眾所周知,strerror()是一個非執行緒安全函式。下面的例程展示了使用特有資料編寫strerror()函式,使其成為執行緒安全函式.例程如下。

#define MAX_ERROR_LEN 256

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;

/* 執行緒終止時會呼叫此函式進行記憶體釋放 */
static void destructor(void *buf)
{
    free(buf);
}

static void createKey(void)
{
    /* 建立特有資料鍵 */
    int s = pthread_key_create(&strerrorKey, destructor);
    if (s != 0){
        printf("key_create error.\n");
        exit(1);
    }
}

void my_strerror(int err)
{
    int s;
    char *buf;

    /* 1. 只初始化一次->建立特有資料鍵 */
    pthread_once(&once, createKey);

    /* 2. 檢視特有資料鍵對應的記憶體是否為空 */
    buf = pthread_getspecific(strerrorKey);
    if (buf == NULL) { //為空則分配記憶體
        buf = malloc(MAX_ERROR_LEN);
        if (buf == NULL){
            exit(1);
        }

        /* 3. 給特有資料鍵繫結記憶體 */
        if (pthread_setspecific(strerrorKey, buf) != 0){
            printf("setspecific error.\n");
        }
    }

    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
    } else {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
    }

    return buf;
}

執行緒區域性儲存

正常情況下我們用static定義的變數,會被存放到程序的初始化資料區,這導致各個執行緒都共享這個變數。而執行緒區域性儲存在定義變數時,使用__thread修飾變數.此時,每個執行緒都會擁有一份對變數的拷貝。執行緒區域性儲存中的變數將一直存在,直至執行緒終止,屆時會自動釋放這一儲存。

區域性儲存的使用更簡單,只需要在定義變數時,使用__thread修飾。如static __thread buf[MAX_ERROR_LEN];.

使用執行緒區域性儲存實現strerror()安全版更簡單。如下:

#define MAX_ERROR_LEN 256
static __thread char buf[MAX_ERROR_LEN];

void my_strerror(int err)
{
    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
    } else {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
    }

    return buf;
}

關於技術交流

此處後的文字已經和題目內容無關,可以不看。
qq群:825695030
微信公眾號:嵌入式的日常
如果上面的文章對你有用,歡迎打賞、點贊、評論。二維碼