天天看點

可重入,異步信号安全,線程安全其它參考文檔

聲明:本文為個人了解,不能保證一定是正确的!

  這三個概念一直糾纏着我,我也時不時的會拿出來辨析下,直到昨天才發現自己可以把它們理順了。是以學習就是這樣一個反複的過程,最終達到頓悟的效果。本文主要參考APUE第三版英文版第10.6和12.5節,以及WIKI百科,還有CSDN和stackoverflow中對這些概念的讨論,然後給出一份自己認為比較合理的了解。

中斷,信号,線程切換

==========================

  1. 這三個概念都牽涉到異步通信,因為運作中的代碼不可預測什麼時候會發生中斷,什麼時候會收到信号,什麼時候會發生線程切換:
    • 中斷,一般指的硬體中斷,是硬體對cpu的中斷
    • 信号,則是對中斷的模拟,可以看作是os對程序的中斷
    • 線程切換,cpu的時分複用手段
  2. 代碼執行流:
#-表示代碼在cpu上運作,.表示等待中

##1. 中斷和信号
<f1>---------...................-----------
<f2>.........-------------------...........
###f1在執行的過程中被f2打斷,當且僅當f2完整的執行完後傳回f1

##2. 線程切換
<f1>----....----....----....----....----
<f2>....----....----....----....----....
###f1和f2互相打斷彼此,交錯地運作
           

可重入,異步資訊安全,線程安全

====================================

  1. 可重入

      可重入的意思就是一個函數沒有執行完,又在另一個地方被調用一次,兩次調用都能得到正确的結果。可重入概念在多任務作業系統之前就已經存在了,It is a concept from the time when no multitasking operating systems existed。

  2. 異步資訊安全,線程安全

      可重入中提到的”另一個地方”可以是:

    • 在中斷或信号中,在此情況下如果函數是可重入的,那麼就稱這個函數是異步信号安全的,這兩種情況可以看成是一種遞歸調用。

      [APUE10.6節就是專門說明異步信号安全的]

    • 在另一個線程中,在此情況下如果函數是可重入的,那麼就稱這個函數是線程安全的。

      [APUE12.5節就是專門說明線程安全的]

    If a function is reentrant with respect to multiple threads, we say that it is thread-safe. This doesn’t tell us, however, whether the function is reentrant with respect to signal handlers. We say that a function that is safe to be reentered from an asynchronous signal handler is async-signal safe.

3. 三者的關系

可重入,異步信号安全,線程安全其它參考文檔

紅色表示不可重入函數,A+B+C表示可重入函數,其中A表示遞歸調用情況下可重入,C表示多線程的情況下可重入,B表示種情況下都可重入。

是以單獨說某函數是重入,而不限定使用場景是沒有意義的;如果僅僅說是非可重入則又是有意義的…

4. 異步信号安全函數清單

可重入,異步信号安全,線程安全其它參考文檔

5. 可重入函數和異步信号安全函數等同嗎?

根據上面的關系圖可以得出兩者是不等同的,可重入函數在不同的情景下可以分為異步信号安全和線程安全。異步信号安全可以看作是遞歸調用情況下的可重入。舉例如下:

//非可重入版本
int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;

    // hardware interrupt might invoke isr() here!!
    *y = t;
}
/
//異步信号安全版本,非線程安全
int t;

void swap(int *x, int *y)
{
    int s;

    s = t; // save global variable
    t = *x;
    *x = *y;

    // hardware interrupt might invoke isr() here!
    *y = t;
    t = s; // restore global variable
}

//在信号處理函數中調用時,每次使用t之前都會進行備份,傳回之前還原t的值;是以在信号處理函數中調用是可重入的;
//如果用線上程中,則有可能線程切換之前未來得及還原t的值,導緻結果出錯,是以線上程切換中是不可重入的;

//由此可以看出,異步信号安全函數不一定是線程安全函數!
//線程安全的函數由于可能使用互斥鎖,在信号處理函數中遞歸調用會出現死鎖,是以線程安全的函數也不一定是異步信号安全的。
           

其它

=====

  1. 信号就像硬體中斷一樣,會打斷正在執行的指令序列。信号處理函數無法判斷捕獲到信号的時候,程序在何處運作。如果信号處理函數中的操作與打斷的函數的操作相同,而且這個操作中有靜态資料結構等,當信号處理函數傳回的時候(當然這裡讨論的是信号處理函數可以傳回),恢複原先的執行序列,可能會導緻信号處理函數中的操作覆寫了之前正常操作中的資料。

    是以通常函數不可重入的原因在于:

    • 函數使用靜态資料結構;
    • 函數調用malloc和free.因為malloc通常會為所配置設定的存儲區維護一個連結表,而插入執行信号處理函數的時候,程序可能正在修改此連結表;
    • 函數是标準IO函數,因為标準IO庫的很多實作都使用了全局資料結構;
    • 函數會修改自身代碼,導緻多次調用不同代碼;
    • 等等。
  2. 即使對于可重入函數,在信号處理函數中使用也需要注意一個問題就是errno。一個線程中隻有一個errno變量,信号處理函數中使用的可重入函數也有可能會修改errno。例如,read函數是可重入的,但是它也有可能會修改errno。是以,正确的做法是在信号處理函數開始,先儲存errno;在信号處理函數退出的時候,再恢複errno。

    例如,程式正在調用printf輸出,但是在調用printf時,出現了信号,對應的信号處理函數也有printf語句,就會導緻兩個printf的輸出混雜在一起。

    如果是給printf加鎖的話,同樣是上面的情況就會導緻死鎖。對于這種情況,采用的方法一般是在特定的區域屏蔽一定的信号。

    屏蔽信号的方法:

    1> signal(SIGPIPE, SIG_IGN); //忽略一些信号

    2> sigprocmask()

    sigprocmask隻為單線程定義的

    3> pthread_sigmask()

    pthread_sigmasks可以在多線程中使用

  3. 很多函數并不是線程安全的,因為他們傳回的資料是存放在靜态的記憶體緩沖區中的。通過修改接口,由調用者自行提供緩沖區就可以使這些函數變為線程安全的。作業系統實作支援線程安全函數的時候,會對POSIX.1中的一些非線程安全的函數提供一些可替換的線程安全版本。

    例如,gethostbyname()是線程不安全的,在Linux中提供了gethostbyname_r()的線程安全實作。

    函數名字後面加上”_r”,以表明這個版本是可重入的(對于線程可重入,也就是說是線程安全的,但并不是說對于信号處理函數也是可重入的,或者是異步信号安全的)。

  4. 多線程程式中常見的疏忽性問題
    • 将指針作為新線程的參數傳遞給調用方棧,我就犯過這樣的錯…
    • 在沒有同步機制保護的情況下通路全局記憶體的共享可更改狀态。
    • 兩個線程嘗試輪流擷取對同一對全局資源的權限時導緻死鎖。其中一個線程控制第一種資源,另一個線程控制第二種資源。其中一個線程放棄之前,任何一個線程都無法繼續操作。
    • 嘗試重新擷取已持有的鎖(遞歸死鎖)。
    • 在同步保護中建立隐藏的間隔。如果受保護的代碼段包含的函數釋放了同步機制,而又在傳回調用方之前重新擷取了該同步機制,則将在保護中出現此間隔。結果具有誤導性。對于調用方,表面上看全局資料已受到保護,而實際上未受到保護。
    • 将UNIX 信号與線程混合時,使用sigwait(2) 模型來處理異步信号。
    • 調用setjmp(3C) 和longjmp(3C),然後長時間跳躍,而不釋放互斥鎖。
    • 從對*_cond_wait() 或*_cond_timedwait() 的調用中傳回後無法重新評估條件。
  5. 如果一個函數對多個線程來說是可重入的,則說這個函數是線程安全的,但這并不能說明對信号處理程式來說該函數也是可重入的。

    如果函數對異步信号處理程式的重入是安全的,那麼就可以說函數是”異步-信号安全”的。

參考文檔

==============

1. APUE第三版原版10.6&12.5,去閱讀原版而不是中文版(有道在手,天下我有:P)

2. https://en.wikipedia.org/wiki/Reentrancy_(computing)

3. http://bbs.csdn.net/topics/310140183

4. http://stackoverflow.com/questions/9837343/difference-between-thread-safe-and-async-signal-safe

5. http://stackoverflow.com/questions/18198487/are-r-unix-calls-reentrant-async-signal-safe-thread-safe-or-both

繼續閱讀