1. 程式人生 > >【Linux】執行緒安全與可重入函式

【Linux】執行緒安全與可重入函式

【Linux學習】:在Linux的一段時間學習中,剛開始是模糊的,所以很久沒有進行部落格的整理,直到最近自己把Linux的學習從前往後回憶與聯絡清楚了,覺得是時候整理成部落格,變為自己的學習筆記了,先從執行緒安全和可重入函式整理,一方面是趁熱打鐵,另一方面是在這篇部落格中,其實巢狀著前面的學習!

第一部分: 執行緒安全

最直接的概念,不管是從網上查閱資料還是書上看到的,都會有這麼一句:一般來說,一個函式是執行緒安全的, 當且僅當被多個併發執行緒反覆呼叫,結果一直是正確的;反之就是不安全的;

一般,我們定義四類執行緒不安全函式:

1、不保護共享變數的函式;
2、函式狀態隨著呼叫改變的函式;
3、返回指向靜態變數指標的函式;
4、呼叫執行緒不安全函式的函式

:通過對執行緒安全的定義和對執行緒安全的判定條件,我們可以想到的就是作業系統中學到的鎖的概念,為什麼要對臨界資源加鎖,就是因為執行緒安全的問題,因為同一程序中的多程序是在同一個地址空間執行的,而多個執行緒共享的資源就很多了,全域性變數,靜態變數就是共享的資源,那麼如果不對共享資源的訪問進行加鎖的話,多執行緒的訪問可能會造成錯誤甚至嚴重的後果(下面舉例說明),那麼,含有全域性變數的這個函式就是執行緒不安全的;多個執行緒之間私有的是各個執行緒的執行的上下文資訊和私有棧,也就是說區域性變數不會影響執行緒安全;

程式碼說明執行緒安全問題(LINUX):
比如說,不安全函式條件1,不對全域性變數進行保護,我們有兩個執行緒,我們希望一個執行緒將count加到500,然後另一個執行緒繼續進行count++,我們希望的結果是1000;

程式碼:
這裡寫圖片描述

:多執行幾次你就會發現,會出現結果不是1000的情況,那麼這個函式就不是執行緒安全的;

程式碼編寫時的注意點:

  1. 要驗證確實是不同的執行緒進行了操作,可以使用pthread_self()函式獲取執行緒的tid;
  2. 有時候,我們會把fun函式編寫為下面這樣,我們發現執行多少次結果都是1000;
void* fun(void* arg)
{
    int i = 0;
    while(i < 500)
    {
        ++i;
        ++count;
        printf("count : %d\n",count);
    }
}

上面這種寫法看不出來效果的原因是:因為當前只有這兩個程序,++count 體現不出來效果,如果有十幾個執行緒的話,就可以看出來了;

在這裡加入一個小知識,也是一道面試題:i++是原子操作嗎?

答案是:no ,i++的操作變成彙編的話其實是三個動作:

1.從記憶體將資料加入暫存器
2.暫存器自加;
3.資料寫會記憶體;

所以不是原子的,在上面的三個動作隨時會被切出去;
至於++i,也不是原子的,但是具體的和底層實現有關!;

所以我們定義一個tmp呢,正是為了干擾,將執行緒不安全體現出來,比如執行緒1將count加到52的時候,剛用tmp儲存了count的值,然後被切出去,程序2又執行count加到200,然後又切到執行緒1,接下來執行緒1執行 count = tmp + 1,注意:執行緒1此時的tmp==52,所以count又被改回去了,這樣兩個執行緒之間就產生了干擾,也就是執行緒不安全;

上面的例子是對全域性變數的多執行緒訪問的舉例,多個執行緒運行同一份函式的時候,不能保證每次的執行結果都是預期的。我們知道執行緒安全是希望多個執行緒對同一區域進行訪問,結果是預期的;

總而言之,執行緒安全就是多執行緒對共享資源的訪問保持互斥與同步的同時,仍然可以確保每次的結果都是正確的;

第二部分: 重入函式

定義:可重入函式主要用於多工環境中,一個可重入的函式簡單來說就是可以被中斷的函式,也就是說,可以在這個函式執行的任何時刻中斷它,轉入OS排程下去執行另外一段程式碼,而返回控制時不會出現什麼錯誤;而不可重入的函式由於使用了一些系統資源,比如全域性變數區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函式是不能執行在多工環境下的。

舉個例子:比如一個執行流正在執行一個函式,而此時來了一個訊號中斷了函式的執行,而去執行訊號的自定義動作,恰好這個自定義動作也是這個函式,如果這個訊號的執行動作執行完之後,返回原執行函式後不會對原執行結果產生影響,並且不會影響全域性和靜態變數的話,那麼這個函式就是可重入函式;

可重入函式就不 列程式碼的例子了;
總之,執行緒安全的函式一定是可重入的;