线程安全存储以及pthread_getspecific/pthread_setspecific
最近閱讀一份linux的線程代碼時,看到了一套函數,pthread_getspecific/pthread_setspecific。光從名字上,完全無法理解出他們到底是干啥的,結合代碼來看,也不是很清楚。于是就去百度。可是,百度來百度去,CSDN來CSDN去,反反復復找了不少資料,卻始終沒能完全理解透徹。感覺大家都是從同一份博客里抄來抄去的,甚至連其中的錯誤都照抄了,不但沒能解答我的疑惑,反而還帶來了其他的問題。
題外話一:這里實在想吐槽一下百度的學術資料搜索能力,跟人家google比,實在是差太多了,找半天都找不到什么有用的資料。廣告搜索能力倒是挺強,各種推廣廣告一堆堆的跳出來,而且是在最顯眼的地方。為什么不能把廣告的搜索能力移植到學術資料的搜索上來呢?
題外話二:這里還想吐槽一下部分CSDN的網友,看到別人的博客,自己也不驗證驗證,消化消化,就直接復制黏貼過來,關鍵也不注明一下是不是轉載的,更不用說貼上源地址了。我找資料時,一個關鍵詞輸進去,鏈接確實能跳出來不少,但是點進去發現TM全是同一篇文章,只是由不同的博主轉來轉去的,絕大部分人甚至連原博文的錯誤都一并轉載了,實在是哭笑不得。
不管如何,自己的疑問還是要搞清楚的。于是繼續查閱資料,并對這兩個函數的用法進行總結和實踐,總算是基本搞清了。下面對我自己的心得進行一下總結,希望能給其他小伙伴提供一下借鑒。
一、技術原理
首先要交代一下,pthread_getspecific/pthread_setspecific這兩個函數,其實是屬于線程數據存儲機制【也叫線程私有數據,英文名Thread Specific Data】的一部分。因此,要先引入線程存儲機制這個大話題,才能進行比較全面而清晰的了解。
大家都知道,在多線程程序中,一個模塊里的全局變量,對于該模塊的所有線程都是可見的,也就是說全局共享的,所有線程都可以使用它,可以隨時改變它的值,這顯然是不安全的,會帶來一些風險。那么有沒有什么辦法能改善這個問題呢?在強大的linux系統下,一切皆有可能!這就到了線程存儲機制隆重登場的時候了!應用了線程存儲機制之后,這個變量表面上看起來還是一個全局變量,所有線程都可以使用它,而它的值在每一個線程中又是單獨隔離的,彼此之間不會互相影響。A線程對該變量的值進行修改之后,相應的變化只會在A線程中體現,其他的線程中去查詢該變量時,得到的依然是本線程中的值,跟A線程改動的值無關。
比如linux系統下常見的errno,它返回標準的錯誤碼。errno肯定不是一個局部變量,因為幾乎每個函數都可以訪問他。但它又不能是一個簡單的全局變量,否則在一個線程里輸出的很可能是另一個線程的出錯信息。因此,必須借助于創建線程的私有數據來解決。線程私有數據采用一鍵多值的技術,即一個鍵對應多個值。訪問數據時都是通過鍵值來訪問,表面上看上去好像是對同一個變量進行訪問,但是實際上訪問的是不同的數據空間。
線程存儲機制的具體用法如下:
下面是前面提到的函數原型:
#include <pthread.h>int pthread_key_create(pthread_key_t *key, void (*destructor)(void *)); int pthread_key_delete(pthread_key_t *key); int pthread_setspecific(pthread_key_t key, const void *value); void *pthread_getspecific(pthread_key_t key);二、實例解析
下面的代碼是從網上摘錄過來,內容很簡單,就是用來驗證這兩個函數的使用。
新建一個模塊命名為test.c,輸入以下內容:
#include <stdio.h> #include <pthread.h> #include <string.h>static pthread_key_t p_key;void *thread_func(void *args) {int *tmp1, *tmp2;/* 注意:這里初始化私有鍵值時使用的特征值,是該線程創建時傳入的特征值a/b */pthread_setspecific(p_key, args); /* 初始化本線程的私有鍵值 */printf("in thread %d. init specific_data to %d\n", *(int *)args, *(int *)args);tmp1 = (int *)pthread_getspecific(p_key); /* 獲取私有鍵值的內容 */printf("get specific_data %d\n", *tmp1);*tmp1 = (*tmp1) * 100; /* 修改私有鍵值的內容 */printf("change specific_data to %d\n", *tmp1);tmp2 = (int *)pthread_getspecific(p_key); /* 重新獲取本線程的私有鍵值 */printf("get specific_data %d\n", *tmp2);return (void *)0; }int main() {int a = 1;int b = 2;pthread_t pa, pb;printf("at first: a = %d. b = %d\n", a, b);pthread_key_create(&p_key, NULL); /* 首先創建私有數據鍵值 */pthread_create(&pa, NULL, thread_func, &a); /* 創建線程1 */pthread_create(&pb, NULL, thread_func, &b); /* 創建線程2 */pthread_join(pa, NULL);pthread_join(pb, NULL);/* 解釋下pthread_join函數:它的目的是使一個線程等待另一個線程結束 *//* 代碼中如果沒有pthread_join主線程會很快結束從而使整個進程結束,創建的線程根本來不及執行 *//* 加入pthread_join后,主線程會一直等待直到等待的線程結束自己才結束,使創建的線程有機會執行 */printf("at last: a = %d. b = %d\n", a, b);return 0; }編譯鏈接并運行,可以看到輸出信息如下:
leon@Ubuntu:~/test$ gcc test.c -lpthread -o test leon@Ubuntu:~/test$ ./test at first: a = 1. b = 2 in thread 1. init specific_data to 1 get specific_data 1 change specific_data to 100 get specific_data 100 in thread 2. init specific_data to 2 get specific_data 2 change specific_data to 200 get specific_data 200 at last: a = 100. b = 200可以看出,兩個線程中分別對私有鍵值進行了初始化、讀取、修改、讀取的過程,所得到的鍵值按照預期的規律發生了變化,同時又彼此獨立互不影響。
三、補充說明
這里要說明一下,不少CSDN網友所轉載的文章里,都對上面的代碼使用“gcc -lpthread test.c -o test”這樣的語句進行編譯鏈接,但是卻沒人發現這里面的問題!!
大家可以自己去編譯一下試試看,如果你輸入“gcc -lpthread test.c -o test”,絕對是編譯不過的,系統會提示:
/tmp/ccSkayWg.o:在函數‘test’中: thread_spc.c:(.text+0x11):對‘pthread_getspecific’未定義的引用 /tmp/ccSkayWg.o:在函數‘thread_func’中: thread_spc.c:(.text+0x54):對‘pthread_setspecific’未定義的引用 thread_spc.c:(.text+0x61):對‘pthread_getspecific’未定義的引用 /tmp/ccSkayWg.o:在函數‘main’中: thread_spc.c:(.text+0xd0):對‘pthread_key_create’未定義的引用 thread_spc.c:(.text+0xed):對‘pthread_create’未定義的引用 thread_spc.c:(.text+0x10a):對‘pthread_create’未定義的引用 thread_spc.c:(.text+0x11b):對‘pthread_join’未定義的引用 thread_spc.c:(.text+0x12c):對‘pthread_join’未定義的引用這是因為gcc依賴順序的問題導致的。就是gcc編譯的時候,各個文件的依賴順序是有講究的。如果文件a依賴于文件b,那么編譯的時候必須把a放前面,b放后面。
例如,在main.c中使用了pthread庫相關函數,那么編譯的時候必須是main.c在前,-lpthread在后:
gcc main.c -lpthread -o a.out所以,上面出現問題的原因就是引入庫的順序出了問題,不能放在前面,得放到后面去:
gcc test.c -lpthread -o test總結
以上是生活随笔為你收集整理的线程安全存储以及pthread_getspecific/pthread_setspecific的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通用门: 与非门、或非门
- 下一篇: RISC-V,芯片中的网红战斗机,究竟是