首頁 > 軟體

Linux 多執行緒可重入函數

2020-06-16 17:29:48

Reentrant和Thread-safe

在單執行緒程式中,整個程式都是順序執行的,一個函數在同一時刻只能被一個函數呼叫,但在多執行緒中,由於並行性,一個函數可能同時被多個函數呼叫,此時這個函數就成了臨界資源,很容易造成呼叫函數處理結果的相互影響,如果一個函數在多執行緒並行的環境中每次被呼叫產生的結果是不確定的,我們就說這個函數是"不可重入的"/"執行緒不安全"的。為了解決這個問題,POSIX多執行緒庫提出了一種機制,用來解決多執行緒環境中的執行緒資料私有化問題,這套機制的主要思想是利用同步和互斥維護一個同名不同值的表,這個表會維護每個執行緒自己的資源地址,表面上是同一個變數,實質上這個變數在不同的執行緒中的地址是不一樣,這樣就保證了每個執行緒其實都在使用自己的資源,實現了"thread-safe"。
其實,隨著多執行緒程式的逐漸流行,除了這種利用系統機制保護執行緒私有資料的方法,還有一部分人重新編寫了一些多執行緒庫函數,這些函數的主要特點就是實現了演算法和資料的分離,函數內部只負責實現演算法,需要的資料由執行緒傳入,這樣就保證了函數的多執行緒安全,eg

char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);        //這個就是asctime的thread-safe版,有_r字尾

但由於介面不同,完全重寫的函數推廣尚需時日。
當下用的更多的是使用_REENTRANT來在原來的函數的基礎上改造,如果編譯的時候定義了這個宏,相關的庫函數就會被編譯成"thread-safe"的版本。

模型

如果要檢視這些函數的man手冊,可以安裝相關的man手冊

pthread_key_t key           //建立用於保護執行緒私有資源的key
pthread_once_t once_key     //建立用於初始化key的once_key,要求用PTHREAD_INIT_ONCE來賦值,否則結果不確定
pthread_key_create()        //建立key
pthread_once()              //初始化key
pthread_getspedifc()        //從key表中獲得執行緒私有資源的地址
pthread_setspedifc()        //將執行緒私有資源的地址放到key中
...

例子

表面上每個函數呼叫了reverse()都會得到rev的地址,其實這個rev地址在不同的執行緒中並不相同,一旦一個執行緒呼叫了reverse()函數,函數首先會到key標識的表中去搜尋這個執行緒以前是否呼叫過這個函數,如果呼叫過,就將表中屬於這個執行緒的rev地址返回,如果沒有,就分配rev,並將該執行緒和它的專屬rev地址註冊到表中,這樣就把reverse()打造成了一個可重入的函數。

#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){
    //建立key
    pthread_key_create(&key,myDestructor);
}
#endif

char* reverse(char* buf,int len){
#ifdef _REENTRANT
    //初始化key
    pthread_once(&once_key,myCreateKey);

    //從key中獲取一個thread-specific的資料
    char* rev=(char*)pthread_getspecific(key);
    if(NULL==rev){
        rev=(char*)malloc(len+1);
        //將thread-specific的資料放到key中
        pthread_setspecific(key,rev);
    }
#else
    static char rev[100];
#endif
    bzero(rev,sizeof(rev));
    //翻轉buf
    while(len--)
        rev[len]=*buf++;
    return rev;
}



void* fcn1(void* p){
    while(1){
        char buf[100]="123456789";
        printf("[%lu]:%sn",pthread_self(),buf);
        char* rev=reverse(buf,strlen(buf));
        sleep(1);
        printf("[%lu]:%sn",pthread_self(),rev);
    }

}

void* fcn2(void* p){
    while(1){
        char buf[100]="abcdef";
        printf("[%lu]:%sn",pthread_self(),buf);
        char* rev=reverse(buf,strlen(buf));
        sleep(2);
        printf("[%lu]:%sn",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;
}

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-11/136663.htm


IT145.com E-mail:sddin#qq.com