1. 程式人生 > >可重入,非同步訊號安全,執行緒安全

可重入,非同步訊號安全,執行緒安全

宣告:本文為個人理解,不能保證一定是正確的!

  這三個概念一直糾纏著我,我也時不時的會拿出來辨析下,直到昨天才發現自己可以把它們理順了。所以學習就是這樣一個反覆的過程,最終達到頓悟的效果。本文主要參考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. 如果一個函式對多個執行緒來說是可重入的,則說這個函式是執行緒安全的,但這並不能說明對訊號處理程式來說該函式也是可重入的。
    如果函式對非同步訊號處理程式的重入是安全的,那麼就可以說函式是”非同步-訊號安全”的。

參考文件

相關推薦

ZZ非同步訊號安全

可重入與非同步訊號安全 From:http://blog.chinaunix.net/u/12592/showart_1871048.html 一個可重入的函式簡單來說就是可以被中斷的函式,也就是說,可以在這個函式執行的任何時刻中斷它,轉入OS排程下去執行另外一段程式碼,而返

呼叫Spring所管理的bean的方法時候為何不會出現執行安全問題?

首先jvm會在記憶體中開闢一塊儲存空間做為執行緒棧空間,每個執行緒都有自己的棧(後進先出)。 呼叫方法時,會在棧中壓入一個棧幀,用來儲存這個方法的引數和區域性變數; 方法返回時 ,棧幀就會彈出,方法的引數和區域性變數就會清除; 方法呼叫時,呼叫棧不斷處於漲落之中,如果呼叫的層級過深,

(轉載)ThreadLocal的實現原理SpringMvc的單例執行安全就是用這個實現的

1. 背景 ThreadLocal原始碼解讀,網上面早已經氾濫了,大多比較淺,甚至有的連基本原理都說的很有問題,包括百度搜索出來的第一篇高訪問量博文,說ThreadLocal內部有個map,鍵為執行緒物件,太誤導人了。 ThreadLocal非常適合對Java多執行緒

非同步訊號安全執行安全

宣告:本文為個人理解,不能保證一定是正確的!   這三個概念一直糾纏著我,我也時不時的會拿出來辨析下,直到昨天才發現自己可以把它們理順了。所以學習就是這樣一個反覆的過程,最終達到頓悟的效果。本文主要參考APUE第三版英文版第10.6和12.5節,以及W

函式SIGCHILD訊號

一、 可重入函式 概念:一個函式被多個執行流進入,不會出錯,這就叫可重入函式;否則,就叫不可重入函式。 1.如果一個函式只訪問自己的區域性變數或引數,當有多個執行流執行時,就不會互相影響。 2.首先它意味著這個函式可以被中斷,其次意味著它除了使用自己棧上的

ReentrantLock鎖(不看後悔看了必懂)

ReentraantLock是通過一個FIFO的等待佇列來管理獲取該鎖所有執行緒的。在“公平鎖”的機制下,執行緒依次排隊獲取鎖(先等待的執行緒先獲得鎖);而“非公平鎖”在鎖是可獲取狀態時,不管自己是不是在佇列的開頭都會獲取鎖。  ReentrantLock和synchroni

10-Synchronized:悲觀鎖

Synchronized:悲觀鎖,可重入鎖 特點:可重入的鎖 可重入鎖,一個獲得的鎖的執行緒沒執行完可以繼續獲得該鎖。 執行緒佔用鎖的時候,如果執行的同步程式碼出現異常,會自動將鎖讓出。 同步程式碼塊的程式碼是同步執行的(一次執行完),而非同步程

Java 中15種鎖的介紹:公平鎖獨享鎖互斥鎖樂觀鎖分段鎖自旋鎖等等

Java 中15種鎖的介紹 在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨享鎖 / 共享鎖 互斥鎖 / 讀寫鎖 樂觀鎖 / 悲觀鎖 分段鎖

JAVA鎖機制-鎖,中斷鎖公平鎖讀寫鎖自旋鎖

部落格引用處(以下內容在原有部落格基礎上進行補充或更改,謝謝這些大牛的部落格指導): JAVA鎖機制-可重入鎖,可中斷鎖,公平鎖,讀寫鎖,自旋鎖 在併發程式設計中,經常遇到多個執行緒訪問同一個 共享資源 ,這時候作為開發者必須考慮如何維護資料一致性,在java中synchronized

Java 種15種鎖的介紹:公平鎖獨享鎖互斥鎖等等...

Java 中15種鎖的介紹 在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨享鎖 / 共享鎖 互斥鎖 / 讀寫鎖 樂觀鎖 / 悲觀鎖 分段鎖 偏向

Java 種15種鎖的介紹:公平鎖獨享鎖互斥鎖等等

Java 中15種鎖的介紹 在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下

老大吩咐的分散式鎖終於完美的實現了!!!

## 重做永遠比改造簡單 最近在做一個專案,將一個其他公司的實現系統(*下文稱作舊系統*),完整的整合到自己公司的系統(*下文稱作新系統*)中,這其中需要將對方實現的功能完整在自己系統也實現一遍。 舊系統還有一批存量商戶,為了不影響存量商戶的體驗,新系統提供的對外介面,還必須得跟以前一致。最後系統完整切換

線程安全

計算 一個 線程 數據 變量 -s 安全 函數 safe thread-safe: 如果一個函數在多線程的條件下仍然保持計算結果和單線程一樣,就說明它是線程安全的。 線程安全的函數:   不包含靜態數據區的變量,只有堆棧變量;   有靜態數據區的變量,然而會加鎖; 可重入:

函數與線程安全

可重入函數 不可重入函數 線程安全 介紹: 一組並發線程運行在同一進程上下文中,每一個線程都有自己獨立的線程上下文,包括線程ID、棧、棧指針、程序計數器、條件碼和通用目的寄存器。每個線程和其他線程一起共享進程上下文的其他部分,包括整個用戶虛擬地址空間(由代碼段、讀/寫數據、堆以及所有共享

linux、異步信號安全和線程安全

ket leave med 指向 多個 提高 post error specific 一 可重入函數 當一個被捕獲的信號被一個進程處理時,進程執行的普通的指令序列會被一個信號處理器暫時地中斷。它首先執行該信號處理程序中的指令。如果從信號處理 程序返回(例如沒有調用exit

Linux函式和執行安全的區別與聯絡(轉)

*****可重入函式      函式被不同的控制流程呼叫,有可能在第一次呼叫還沒返回時就再次進入該函式,這稱為重入。      當程式執行到某一個函式的時候,可能因為硬體中斷或者異常而使得在使用者正在執行的程式

執行安全

  ----------可重入------------ 概念:   可重入函式,即函式(操作)中斷後,再次進入該函式繼續執行仍能得到正確的結果,這裡強調的是中斷後能正確執行。 舉個例子:   程式執行到某個函式foo()時,收到訊號,於是暫停目前正在執行的函式,轉到訊號處理函式,而這個訊號

synchronized後丟擲異常鎖釋放了嗎

synchronized用於同步方法或者程式碼塊,使得多個執行緒在試圖併發執行同一個程式碼塊的時候,序列地執行。以達到執行緒安全的目的。 在多執行緒的時候是這樣的,但是對於單執行緒,是允許重入的,每重入一次,計數器加1,當退出程式碼塊時,計數器減1。 那正常退出時計數器減1,拋異常時計數器也是減1。那如果

執行安全佇列與執行池串想

題中這三者是有一環扣一環的聯絡的,在此做一個總結加深理解。 再入鎖Reentrantlock主要是和synchronized關鍵字作區別,都是加鎖但是排程單位不同。synchronized是以呼叫次數為單位,即被synchronized修飾的方法或者程式碼塊每被執行緒執行一次,都有一個獲取鎖釋放

執行安全

維基百科:https://zh.wikipedia.org/wiki/%E5%8F%AF%E9%87%8D%E5%85%A5 1、執行緒安全 若一個程式或子程式可以“在任意時刻被中斷然後作業系統排程執行另外一段程式碼,這段程式碼又呼叫了該子程式不會出錯”,則稱其為可重入(reentrant或re-entr