執行緒控制之執行緒私有資料

2022-05-04 20:06:20 字數 4354 閱讀 3828

執行緒私有資料(也稱執行緒特定資料)是儲存和查詢與某個執行緒相關的資料的一種機制。把這種資料稱為執行緒私有資料或執行緒特定資料的原因是:希望每個執行緒可以獨立地訪問資料副本,而不需要擔心與其他執行緒的同步訪問問題。

執行緒模型促進了程序中資料和屬性的共享,許多人在設計執行緒模型時會遇到各種麻煩。但在這樣的模型中,為什麼還需要提出一些合適的用於阻止共享的介面呢?其中有兩個原因:

第一,有時候需要維護基於每個執行緒的資料。

採用執行緒私有資料的第二個原因是:它提供了讓基於程序的介面適應多執行緒環境的機制。乙個很明顯的例項就是errno。回憶中對errno的討論,(執行緒出現)以前的介面把errno定義為程序環境中全域性可訪問的整數。系統呼叫和庫例程在呼叫或執行失敗時設定errno,把它作為操作失敗時的附屬結果。為了讓執行緒也能夠使用那些原本基於程序的的系統呼叫和庫例程,errno被重新定義為執行緒私有資料。這樣,乙個執行緒做了設定errno的操作並不會影響程序中其他執行緒的errno的值

程序中的所有執行緒都可以訪問程序的整個位址空間。除了使用暫存器以外,執行緒沒有辦法阻止其他執行緒訪問它的資料,執行緒私有資料也不例外。雖然底層的實現部分並不能阻止這種訪問能力,但管理執行緒私有資料的函式可以提高執行緒間的資料獨立性。

在分配執行緒私有資料之前,需要建立與該資料關聯的鍵這個鍵將用於獲取對執行緒私有資料的訪問權。使用pthread_key_create建立乙個鍵。

#include int pthread_key_create(pthread_key_t *keyp,

void (*destructor)(void *));

返回值:若成功則返回0,否則返回錯誤編號

建立的鍵存放在keyp指向的記憶體單元,這個鍵可以被程序中的所有執行緒使用,但每個執行緒把這個鍵與不同的執行緒私有資料位址進行關聯(如何關聯???)。建立新鍵時,每個執行緒的資料位址設為null值。

除了建立鍵以外,pthread_key_create可以選擇為該鍵關聯析構函式,當執行緒退出時,如果資料位址已經被置為非null數值,那麼析構函式就會被呼叫,它唯一的引數就是該資料位址。如果傳入的destructor引數為null,就表明沒有析構函式與鍵關聯。當執行緒呼叫pthread_exit或者執行緒執行返回,正常退出時,析構函式就會被呼叫,但如果執行緒呼叫了exit、_exit、_exit、abort或出現其他非正常的退出時(關於正常退出與不正常退出:就不會呼叫析構函式。

執行緒通常使用malloc為執行緒私有資料分配記憶體空間,析構函式通常釋放已分配的記憶體。如果執行緒沒有釋放記憶體就退出了,那麼這塊記憶體將會丟失,即執行緒所屬程序出現了記憶體洩漏。

執行緒可以為執行緒私有資料分配多個鍵,每個鍵都可以有乙個析構函式與它關聯。各個鍵的析構函式可以互不相同,當然它們也可以使用相同的析構函式。每個作業系統在實現的時候可以對程序可分配的鍵的數量進行限制(回憶中表12-1中的pthread_keys_max)。

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

對所有的執行緒,都可以通過呼叫pthread_key_delete來取消鍵與執行緒私有資料值之間的關聯關係。

#include int pthread_key_delete(pthread_key_t *key);

返回值:若成功則返回0,否則返回錯誤編號

注意,呼叫pthread_key_delete並不會啟用與鍵關聯的析構函式。要釋放任何與鍵對應的執行緒私有資料值的記憶體空間,需要在應用程式中採取額外的步驟。

需要確保分配的鍵並不會由於在初始化階段的競爭而發生變動。下列**可以導致兩個執行緒都呼叫pthread_key_create:

void destructor(void *);

pthread_key_t key;

int init_done = 0

;int

threadfunc(

void *arg)

...}

有些執行緒可能看到某個鍵值,而其他的執行緒看到的可能是另乙個不同的鍵值,這取決於系統是如何排程執行緒的,解決這種競爭的辦法是使用pthread_once。

#include pthread_once_t initflag =pthread_once_init;

int pthread_once(pthread_once_t *initflag, void (*initfn)(void

));返回值:若成功則返回0,否則返回錯誤編號

initflag必須是乙個非本地變數(即全域性變數或靜態變數),而且必須初始化為pthread_once_init。

如果每個執行緒都呼叫pthread_once,系統就能保證初始化例程initfn只被呼叫一次,即在系統首次呼叫pthread_once時。建立鍵時避免出現競爭的乙個恰當的方法可以描述如下:

void destructor(void *);

pthread_key_t key;

thread_once_t init_done =pthread_once_init;

void

thread_init(

void

)int

threadfunc(

void *arg)

鍵一旦建立,就可以通過pthread_setspecific函式把鍵和執行緒私有資料關聯起來。可以通過pthread_getspecific函式獲得執行緒私有資料的位址。

#include void *pthread_getspecific(pthread_key_t key);

返回值:執行緒私有資料值;若沒有值與鍵關聯則返回null

int pthread_setspecific(pthread_key_t key, const

void *value);

返回值:若成功則返回0,否則返回錯誤編號

如果沒有執行緒私有資料值與鍵關聯,pthread_getspecific將返回乙個空指標,可以據此來確定是否需要呼叫pthread_setspecific。

例項

程式清單12-5 執行緒安全的getenv的相容版本(使用執行緒私有資料來維護每個執行緒的資料緩衝區的副本,用於存放各自的返回字串)

#include #include 

#include

#include

static

pthread_key_t key;

static pthread_once_t init_done =pthread_once_init;

pthread_mutex_t env_mutex =pthread_mutex_initializer;

extern

char **environ;

static

void

pthread_init(

void

)char *getenv(

const

char *name)

pthread_setspecific(key, envbuf);

}len =strlen(name);

for(i = 0; environ[i] != null; i++)

}

pthread_mutex_unlock(&env_mutex);

return

(null);

}

使用pthread_once來確保只為將要使用的執行緒私有資料建立了乙個鍵。如果pthread_getspecific返回的是空指標,需要分配記憶體然後把鍵與該記憶體單元關聯,否則如果返回的不是空指標,就是用pthread_getspecific返回的記憶體單元。對析構函式,使用free來釋放之前由malloc分配的記憶體。只有當執行緒私有資料值為非null時,析構函式才會被呼叫。

注意,雖然這個版本的getenv是執行緒安全的,但它並不是非同步-訊號安全的。對訊號處理程式而言,即使使用遞迴的互斥量,這個版本的getenv也不可能是可重入的,因為它呼叫了malloc,而malloc函式本身並不是非同步-訊號安全的。

本篇博文內容摘自《unix環境高階程式設計》(第二版),僅作個人學習記錄所用。關於本書可參考:

執行緒控制 私有資料

執行緒私有資料採用了一種被稱為一鍵多值的技術,即乙個鍵對應多個數值。訪問資料時都是通過鍵值來訪問,好像是對乙個變數進行訪問,其實是在訪問不同的資料。使用執行緒私有資料時,首先要為每個執行緒資料建立乙個相關聯的鍵。在各個執行緒內部,都使用這個公用的鍵來指代執行緒資料,但是在不同的執行緒中,這個鍵代表的...

執行緒特定 私有 資料

簡單的講,執行緒特定 私有 資料是每個執行緒的區域性變數,更改執行緒a中的執行緒特定 私有 資料,並不會影響到執行緒b中相對應的執行緒特定 私有 資料。常見的errno就是執行緒特定 私有 資料,每個執行緒重置errno的操作並不會影響程序中其他執行緒的errno值。下面介紹下執行緒特定資料的使用技...

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

thread specific data tsd 執行緒私有資料,有什麼用呢?在多執行緒中,經常要用全域性變數來實現多個函式間的資料共享。由於資料空間是共享的,因此全域性變數也為所有程序共有。但有時應用程式設計中必要提供執行緒私有的全域性變數,這個變數被各個執行緒私有,但卻可以跨過多個函式訪問。書上...