多執行緒程式處於乙個多變的環境當中,可訪問的全域性變數和堆資料隨時可能被其他執行緒改變。資料同步從根本上說是乙個很難的問題,它引出了在普遍的順序中不會出現的問題,執行緒安全應運而生。
執行緒安全
執行緒安全是多個執行緒訪問時,採用了加鎖機制,當乙個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問直到該執行緒讀取結束,其他執行緒才可使用,保證了資料的一致性。
與之對應的則是執行緒不安全
,對資料的訪問不提供保護機制,導致多個執行緒先後更改資料造成資料的不一致問題,這是乙個非常嚴重的問題。
從二元訊號
到生產者消費者模型
都存在很嚴重的執行緒不安全
問題,不過那只是執行緒安全性問題的乙個縮影。下面我們來看乙個多執行緒的i++操作:
#include
#include
#include
#include
int count =0;
void
*pthread_run
(void
*arg)
return
null;}
intmain()
i++操作我們只需要三步操作:
1)讀取i到某個暫存器中。
2)i++。
3)將暫存器中的內容儲存回i中。
從實驗結果可以看出,兩個執行緒併發執行,程式在使用者態與核心態之間不斷的切換,兩個執行緒對其進行訪問,就會造成資料的不一致問題。
如果你看了i++的彙編**就會發現,i++操作並不是一條指令,即非原子的。作業系統執行i++的時候可能執行了一半就被排程去執行另乙個**,而單條指令是不會被打斷的。
解決方式是:① 將i++操作換成inc指令,可以直接增加乙個記憶體單元值,在win裡,也提供了一套interlocked api專門處理原子操作。
② 對臨界區加鎖(二元訊號量、互斥鎖、多元訊號量等均可)。
下面我們使用互斥鎖對臨界區進行加鎖保護:
#include
#include
#include
#include
//互斥鎖的初始化
這樣一來,就解決了多執行緒訪問時造成的資料不一致問題,當然,這僅僅是執行緒安全的冰山一角。
我們能夠定義出四個(不相交的)執行緒不安全函式類:
1)不保護共享變數的函式。
比如上述程式中的i++操作。
解決方法: 對臨界區加鎖,或者使用pv操作來保護共享的變數。
2)保持跨越多個呼叫的狀態的函式。
比如乙個偽隨機數生成器,當呼叫srand為rand設定乙個種子後,如果多執行緒呼叫rand函式,就會造成執行緒的安全隱患。
解決方法:重寫rand函式,使得它不再使用任何static資料,而是依靠呼叫者在引數中傳遞狀態資訊。
3)返回指向靜態變數的指標的函式。
比如將乙個計算結果放在乙個static變數中,然後返回乙個指向這個變數的指標。如果多執行緒呼叫這些函式,正在被乙個執行緒使用的結構會被另乙個執行緒覆蓋掉。
解決方法:① 選擇重寫函式,使得呼叫者傳遞存放結果的變數的位址,消除了所有共享資料。 ② 使用加鎖-拷貝(lock-and-copy)技術。將執行緒不安全函式與互斥鎖聯絡起來,在每乙個呼叫位置,對互斥鎖加鎖,呼叫執行緒不安全函式,將函式返回的結果拷貝到乙個私有的儲存器位置,然後對互斥鎖解鎖。
4)呼叫執行緒不安全函式的函式。
我們假設函式a安全,函式b不安全。情況①:如果函式a呼叫b,那麼a不一定不安全。如果b是第2)累的函式,即依賴於跨越多次呼叫的狀態,那麼a執行緒肯定不安全。解決方法:對函式b進行重寫。
情況②:如果b是第1)類或者第3)類。解決方法:需要用互斥鎖保護呼叫位置和任何得到的共享資料,a仍可能是執行緒安全的。
可重入函式
可重入函式:可以重複進入。這個函式不僅可以被中斷,而且除了使用自己棧上的變數以外不依賴於任何環境(包括static)。可以允許有多個函式的副本在執行,由於他們使用的是分離的棧,因此不會互相干擾。
不可重入函式:一重入就出錯。由於使用了一些系統資源,比如全域性變數區,中斷向量表等,如果被中斷,是不能再多工環境下生存的。
很多時候,可重入函式與縣城安全被用作同義詞,但是他們還是有很明顯的區別的,可重入函式僅僅是執行緒安全函式的乙個真子集,如下圖所示:
乙個函式要想被重入,只有兩種情況:
·1)多個執行緒同時執行這個函式。
2)函式自身(可能是經過多層呼叫之後)呼叫本身。
乙個函式之所以可重入,則表明了重入對該函式不會造成任何的不良影響。
乙個函式稱為可重入的充要條件:
① 不是任何(區域性)靜態或全域性的非const變數。
② 不返回任何(區域性)靜態或全域性的非const變數的指標。
③ 僅依賴於呼叫方提供的引數。
④ 不依賴任何單個資源的鎖(mutex等)。
⑤ 不呼叫任何不可重入的函式。
可重入是併發安全的強力保障,乙個可重入的函式可以在多執行緒環境下放心使用。
可重入函式與執行緒安全函式的區別
1)可重入函式屬於執行緒安全函式的乙個真子集,即可重入函式是執行緒安全的充分條件。
2)執行緒安全是多個執行緒下引起的,但可重入函式可以在只有乙個執行緒的情況下發生。
3)若乙個函式中存在全域性變數,那麼這個函式不是執行緒安全函式。
4)執行緒安全函式能夠使不同的執行緒訪問同一塊位址空間,而可重入函式要求不同的執行流對資料的操作互不影響使結果是相同的。
執行緒安全與可重入函式
可重入函式 reentrant function 與執行緒安全函式 thread safe function 有時容易混淆,而且各種文件中的解釋也不是很清楚,這裡根據筆者的經驗來說明一下。執行緒安全函式 概念 執行緒安全的概念比較直觀。一般說來,乙個函式被稱為執行緒安全的,當且僅當被多個併發執行緒反...
可重入函式與執行緒安全
執行緒安全 乙個函式被稱為執行緒安全的 thread safe 當且僅當被多個併發程序反覆呼叫時,它會一直產生正確的結果。如果乙個函式不是執行緒安全的,我們就說它是執行緒不安全的 thread unsafe 我們定義四類 有相交的 執行緒不安全函式。將這類執行緒不安全函式變為執行緒安全的,相對比較容...
可重入函式與執行緒安全
可重入函式與執行緒安全 執行緒安全 假如在乙個函式中它是這麼寫的,在乙個全域性鍊錶上存放資料,在單執行緒模式下,我們先new乙個新的節點然後讓head next指向這個節點,這種場景在多執行緒場景下會是這樣的過程,執行緒一new了乙個節點,然後cpu轉去執行執行緒二,執行緒二new乙個節點後head...