linux執行緒特有資料(key)與執行緒區域性儲存(__thread)
阿新 • • 發佈:2020-12-07
技術標籤:初學linux執行緒特有資料 key區域性儲存__thread
執行緒特有資料(key)與執行緒區域性儲存(__thread)
要了解執行緒特有資料
與區域性儲存
的特性。首先需要了解什麼是執行緒安全函式
,什麼是非執行緒安全函式
。
執行緒安全函式
:函式可同時供多個執行緒安全呼叫.即可重入,函式重入時不會造成函式的邏輯混亂。非執行緒安全函式
:如果不是執行緒安全函式,那麼他就是非執行緒安全函式。導致非安全的主要原因是函式使用了全域性變數,記憶體分配等,在多執行緒並行訪問此函式時,會造成此函式的邏輯錯誤。
將一個非執行緒安全函式轉換為執行緒安全函式的方法很多,如:
使用互斥量
。對函式中的共享資源進行保護,保證函式邏輯的正確性。- 缺點:線上程遇到互斥量時,並行訪問就轉換成了順序訪問。
- 避免使用全域性和靜態變數等會造成函式不可重入的機制。
使用執行緒特有資料與區域性儲存
。一般用於庫函式的編寫中。
一般庫函式的編寫中,可能會用到執行緒特有資料與區域性儲存。
執行緒特有資料
執行緒特有資料:簡單的說,就是為每個呼叫執行緒分別維護一份變數的副本。每個執行緒通過特有資料鍵訪問時,這個特有資料鍵都會獲取到本執行緒繫結的變數副本。
執行緒特有資料的使用:
- 首先通過pthread_key_create()函式建立初始化一個特有資料鍵,並繫結解構函式(執行緒在中止時呼叫,一般用於記憶體釋放等)。
- 然後通過pthread_getspecific()繫結鍵與變數實體。
- 最後通過pthread_getspecific()函式獲取變數實體並訪問。
通過特有資料鍵來獲取與訪問。特有資料鍵是全域性的。
int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
- 建立一個特有資料鍵。用於之後繫結特有資料。
- key:特有資料鍵實體指標。
- destructor:解構函式。執行緒終止時呼叫。
- return:
int pthread_setspecific(pthread_key_t key, const void *value)
- 繫結特有資料鍵與資料實體
- key:特有資料鍵
- value:資料內容實體。
void *pthread_getspecific(pthread_key_t key)
- 獲取指定鍵對應的資料內容。如果未繫結資料實體。則返回NULL。可以利用這一點來判斷自身是否是初次為某個執行緒所呼叫,若為初次,
則必須為該執行緒分配空間。 - 返回資料實體指標。如果未繫結資料實體。則返回NULL。
- 獲取指定鍵對應的資料內容。如果未繫結資料實體。則返回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
微信公眾號:嵌入式的日常
如果上面的文章對你有用,歡迎打賞、點贊、評論。