1. 程式人生 > >深入理解執行緒安全和可重入函式

深入理解執行緒安全和可重入函式

執行緒安全

基本定義

執行緒安全:簡單來說執行緒安全就是多個執行緒併發同一段程式碼時,不會出現不同的結果,我們就可以說該執行緒是安全的;

執行緒不安全:說完了執行緒安全,執行緒不安全的問題就很好解釋,如果多執行緒併發執行時會產生不同的結果,則該執行緒就是不安全的。

執行緒安全產生的原因:大多是因為對全域性變數和靜態變數的操作

常見的執行緒不安全的函式

(1)不保護共享變數的函式

(2)函式狀態隨著被呼叫,狀態發生變化的函式

(3)返回指向靜態變數指標的函式

(4)呼叫執行緒不安全函式的函式

常見的執行緒安全的情況

(1)每個執行緒對全域性變數或者靜態變數只有讀取的許可權,而沒有寫入的許可權,一般來說這些執行緒是安全的;

(2)類或者介面對於執行緒來說都是原子操作;

(3)多個執行緒之間的切換不會導致該介面的執行結果存在二義性;

程式碼演示

#include<stdio.h>
#include<pthread.h>

int value=0;

void* func(void* arg){
        int i=0;
        while(i<10000){
                int tmp=value;
                value=i;
                printf("value is %d\n",value);
                value=tmp+1;
                i++;
        }
}

int main()
{
        pthread_t id1,id2;
        pthread_create(&id1,NULL,func,NULL);
        pthread_create(&id2,NULL,func,NULL);

        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
        printf("value is %d\n",value);
        return 0;
}

執行結果(可見在存線上程安全時得到的結果並不是我們所期待的):


可重入函式

基本定義

重入:同一個函式被不同的執行流呼叫,當前一個流程還沒有執行完,就有其他的程序已經再次呼叫(執行流之間的相互巢狀執行);

可重入:多個執行流反覆執行一個程式碼,其結果不會發生改變,通常訪問的都是各自的私有棧資源;

不可重入:多個執行流反覆執行一段程式碼時,其結果會發生改變;

可重入函式:當一個執行流因為異常或者被核心切換而中斷正在執行的函式而轉為另外一個執行流時,當後者的執行流對同一個函式的操作並不影響前一個執行流恢復後執行函式產生的結果;

不可重入函式:當程式執行到某一個函式的時候,可能因為硬體中斷或者異常而使得在使用者正在執行的程式碼暫時終端轉而進入你核心,這個時候如有一個訊號需要被處理,而處理的這個訊號的時候又會重新呼叫剛才中斷的函式,如果

函式內部有一個全域性變數需要被操作,那麼,當訊號處理完成之後重新返回使用者態恢復中斷函式的上下文再次繼續執行的時候,對同一個全域性變數的操作結果可能就會發生改變而並不如我們預期的那樣,這樣的函式被稱為不可重入函式。例如在進行連結串列的插入時,插入函式訪問一個全域性連結串列,有可能因為重入而造成錯亂。

可重入函式滿足條件

(1)不使用全域性變數或靜態變數;
(2)不使用用malloc或者new開闢出的空間;
(3)不呼叫不可重入函式;
(4)不返回靜態或全域性資料,所有資料都有函式的呼叫者提供;
(5)使用本地資料,或者通過製作全域性資料的本地拷貝來保護全域性資料;

不可重入函式符合以下條件之一


(1)呼叫了malloc/free函式,因為malloc函式是用全域性連結串列來管理堆的。
(2)呼叫了標準I/O庫函式,標準I/O庫的很多實現都以不可重入的方式使用全域性資料結構
(3)可重入體內使用了靜態的資料結構。

可重入函式分類

(1)顯式可重入函式


如果所有函式的引數都是傳值傳遞的(沒有指標),並且所有的資料引用都是本地的自動棧變數(也就是說沒有引用靜態或全域性變數),那麼函式就是顯示可重入的,也就是說不管如何呼叫,我們都可斷言它是可重入的。


(2)隱式可重入函式


可重入函式中的一些引數是引用傳遞(使用了指標),也就是說,在呼叫執行緒小心地傳遞指向非共享資料的指標時,它才是可重入的。
可重入函式可以有多餘一個任務併發使用,而不必擔心資料錯誤,相反,不可重入函式不能由超過一個任務所共享,除非能確保函式的互斥(或者使用訊號量,或者在 程式碼的關鍵部分禁用中斷)。可重入函式可以在任意時刻被中斷,稍後再繼續執行,不會丟失資料,可重入函式要麼使用本地變數,要麼在使用全域性變數時保護自己 的資料。

程式碼演示:

#include<stdio.h>
#include<signal.h>

int value=0;

void fun(){
        int i=0;
        while(i++<5){
                value++;
                printf("value is %d\n",value);
                sleep(1);
        }
}

int main()
{
        signal(2,fun);
        fun();
        printf("the value is %d\n",value);
        return 0;
}

執行結果對比:


可重入函式與執行緒安全的區別與聯絡

聯絡:

函式可以是可重入的,是執行緒安全的,或者二者皆是,或者二者皆非。不可重入的函式不能由多個執行緒使用。另外,或許不可能讓某個不可重入的函式是執行緒安全的。

區別:

(1)可重入函式是執行緒安全函式的一種,其特點在於它們被多個執行緒呼叫時,不會引用任何共享資料。
(2)執行緒安全是在多個執行緒情況下引發的,而可重入函式可以在只有一個執行緒的情況下來說。
(3)執行緒安全不一定是可重入的,而可重入函式則一定是執行緒安全的。
(4)如果一個函式中有全域性變數,那麼這個函式既不是執行緒安全也不是可重入的。
(5)如果將對臨界資源的訪問加上鎖,則這個函式是執行緒安全的,但如果這個重入函式若鎖還未釋放則會產生死鎖,因此是不可重入的。
(6)執行緒安全函式能夠使不同的執行緒訪問同一塊地址空間,而可重入函式要求不同的執行流對資料的操作互不影響使結果是相同的。