Linux多線程可重入函數(shù)
Reentrant和Thread-safe
在單線程程序中,整個(gè)程序都是順序執(zhí)行的,一個(gè)函數(shù)在同一時(shí)刻只能被一個(gè)函數(shù)調(diào)用,但在多線程中,由于并發(fā)性,一個(gè)函數(shù)可能同時(shí)被多個(gè)函數(shù)調(diào)用,此時(shí)這個(gè)函數(shù)就成了臨界資源,很容易造成調(diào)用函數(shù)處理結(jié)果的相互影響,如果一個(gè)函數(shù)在多線程并發(fā)的環(huán)境中每次被調(diào)用產(chǎn)生的結(jié)果是不確定的,我們就說這個(gè)函數(shù)是"不可重入的"/"線程不安全"的。為了解決這個(gè)問題,POSIX多線程庫提出了一種機(jī)制,用來解決多線程環(huán)境中的線程數(shù)據(jù)私有化問題,這套機(jī)制的主要思想是利用同步和互斥維護(hù)一個(gè)同名不同值的表,這個(gè)表會(huì)維護(hù)每個(gè)線程自己的資源地址,表面上是同一個(gè)變量,實(shí)質(zhì)上這個(gè)變量在不同的線程中的地址是不一樣,這樣就保證了每個(gè)線程其實(shí)都在使用自己的資源,實(shí)現(xiàn)了"thread-safe"。
其實(shí),隨著多線程程序的逐漸流行,除了這種利用系統(tǒng)機(jī)制保護(hù)線程私有數(shù)據(jù)的方法,還有一部分人重新編寫了一些多線程庫函數(shù),這些函數(shù)的主要特點(diǎn)就是實(shí)現(xiàn)了算法和數(shù)據(jù)的分離,函數(shù)內(nèi)部只負(fù)責(zé)實(shí)現(xiàn)算法,需要的數(shù)據(jù)由線程傳入,這樣就保證了函數(shù)的多線程安全,eg
- char *asctime(const struct tm *tm);
- char *asctime_r(const struct tm *tm, char *buf); //這個(gè)就是asctime的thread-safe版,有_r后綴
但由于接口不同,完全重寫的函數(shù)推廣尚需時(shí)日。
當(dāng)下用的更多的是使用_REENTRANT來在原來的函數(shù)的基礎(chǔ)上改造,如果編譯的時(shí)候定義了這個(gè)宏,相關(guān)的庫函數(shù)就會(huì)被編譯成"thread-safe"的版本。
模型
如果要查看這些函數(shù)的man手冊,可以安裝相關(guān)的man手冊
- pthread_key_t key //創(chuàng)建用于保護(hù)線程私有資源的
- keypthread_once_t once_key //創(chuàng)建用于初始化key的once_key,要求用PTHREAD_INIT_ONCE來賦值,否則結(jié)果不確定
- pthread_key_create() //創(chuàng)建
- keypthread_once() //初始化
- keypthread_getspedifc() //從key表中獲得線程私有資源的地址
- pthread_setspedifc() //將線程私有資源的地址放到key中...
例子
表面上每個(gè)函數(shù)調(diào)用了reverse()都會(huì)得到rev的地址,其實(shí)這個(gè)rev地址在不同的線程中并不相同,一旦一個(gè)線程調(diào)用了reverse()函數(shù),函數(shù)首先會(huì)到key標(biāo)識(shí)的表中去搜索這個(gè)線程以前是否調(diào)用過這個(gè)函數(shù),如果調(diào)用過,就將表中屬于這個(gè)線程的rev地址返回,如果沒有,就分配rev,并將該線程和它的專屬rev地址注冊到表中,這樣就把reverse()打造成了一個(gè)可重入的函數(shù)。
- #include<stdio.h>
- #include<pthread.h>
- #include<stdlib.h>
- #include<string.h>
- pthread_key_t key;
- pthread_once_t once_key=PTHREAD_ONCE_INIT;
- #ifdef _REENTRANT
- void myDestructor(void*p){
- free(p);
- }
- void myCreateKey(void){ //創(chuàng)建key
- pthread_key_create(&key,myDestructor);
- }
- #endif
- char* reverse(char* buf,int len){
- #ifdef _REENTRANT
- //初始化key
- pthread_once(&once_key,myCreateKey); //從key中獲取一個(gè)thread-specific的數(shù)據(jù)
- char* rev=(char*)pthread_getspecific(key);
- if(NULL==rev){
- rev=(char*)malloc(len+1); //將thread-specific的數(shù)據(jù)放到key中
- pthread_setspecific(key,rev);
- }
- #else
- static char rev[100];
- #endif
- bzero(rev,sizeof(rev)); //翻轉(zhuǎn)buf
- while(len--)
- rev[len]=*buf++;
- return rev;
- }
- void* fcn1(void* p){
- while(1){
- char buf[100]="123456789";
- printf("[%lu]:%s\n",pthread_self(),buf);
- char* rev=reverse(buf,strlen(buf));
- sleep(1);
- printf("[%lu]:%s\n",pthread_self(),rev);
- }
- }
- void* fcn2(void* p){
- while(1){
- char buf[100]="abcdef";
- printf("[%lu]:%s\n",pthread_self(),buf);
- char* rev=reverse(buf,strlen(buf));
- sleep(2);
- printf("[%lu]:%s\n",pthread_self(),rev);
- }
- }int main(int argc, const char *argv[]){
- pthread_t tid[4];
- pthread_create(&tid[0],NULL,fcn1,NULL);
- pthread_create(&tid[1],NULL,fcn2,NULL);
- pause();
- return 0;
- }