線程安全
在多線程編程中,線程安全是必須要考慮的因素。
什么是線程安全?
在多線程環(huán)境中,多個(gè)線程在同一時(shí)刻對(duì)同一份資源進(jìn)行寫操作時(shí),不會(huì)出現(xiàn)數(shù)據(jù)不一致。反之,則是線程非安全的。
線程安全是程序設(shè)計(jì)中的術(shù)語(yǔ),指某個(gè)函數(shù)、函數(shù)庫(kù)在多線程環(huán)境中被調(diào)用時(shí),能夠正確地處理多個(gè)線程之間的公用變量,使程序功能正確完成。
為了確保在多線程環(huán)境中的線程安全,就要確保數(shù)據(jù)的一致性。確保線程安全的幾種方法:
使用互斥鎖
一個(gè)線程,如果需要訪問(wèn)公共資源,需要獲得互斥鎖并對(duì)其加鎖,資源在在鎖定過(guò)程中,如果其它線程對(duì)其進(jìn)行訪問(wèn),也需要獲得互斥鎖,如果獲取不到,線程只能進(jìn)行阻塞,直到獲得該鎖的線程解鎖。
#include
int increment_counter(void)
{
static int counter = 0;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// only allow one thread to increment at a time
++counter;
// store value before any other threads increment it further
int result = counter;
pthread_mutex_unlock(&mutex);
return result;
}
這個(gè)函數(shù)是線程安全的,可以在多個(gè)線程中被調(diào)用。
使用原子操作
上面的例子中,使用一個(gè) 互斥鎖來(lái)保護(hù)一次簡(jiǎn)單的增量操作顯然過(guò)于昂貴,我們可以使用一些專門的原子操作API函數(shù)來(lái)替代。如上述例子,c++11中的原子變量提供了一個(gè)可使此函數(shù)既線程安全又可重入(而且還更簡(jiǎn)潔)的替代方案:
#include
int increment_counter(void)
{
static std::atomic<int> counter(0);
// increment is guaranteed to be done atomically
int result = ++counter;
return result;
}
Linux內(nèi)核中原子整形操作:
#include
int increment_counter(void)
{
atomic_t counter = ATOMIC_INIT(0);
// increment is guaranteed to be done atomically
atomic_inc(&counter);
int result = counter;
return result;
}
什么是原子操作?
從字面上簡(jiǎn)單理解,原子是一種很微小的粒子;原子操作是不能再進(jìn)一步細(xì)分的操作。
從上面互斥鎖的例子來(lái)看,在線程層面,線程1和線程2同時(shí)調(diào)用了increment_counter函數(shù),被 mutex 保護(hù)的操作是原子操作,lock、unlock及保護(hù)部分要整體順序運(yùn)行,不可再進(jìn)一步細(xì)分,作為一個(gè)原子存在 。
如果確定某個(gè)操作是原子的,并且有原子操作API函數(shù)可以使用,就不用為了去保護(hù)這個(gè)操作而加上會(huì)耗費(fèi)昂貴性能開(kāi)銷的鎖。
如,Linux內(nèi)核原子整形操作 API 函數(shù)表(來(lái)源:正點(diǎn)原子) :
防止過(guò)度優(yōu)化
線程安全的函數(shù)應(yīng)該為每個(gè)調(diào)用它的線程分配專門的空間,把多個(gè)線程共享的變量正確對(duì)待(如,通知編譯器該變量為“易失(volatile)”型,阻止其進(jìn)行一些不恰當(dāng)?shù)膬?yōu)化)。
線程安全函數(shù)與可重入函數(shù)?
先明確概念:
- 線程安全函數(shù):能夠正確地處理多個(gè)線程之間的公用變量的函數(shù)。、
- 可重入函數(shù):在任意時(shí)刻被中斷然后操作系統(tǒng)調(diào)度執(zhí)行另一段代碼,這段代碼又使用了該副程序不會(huì)出錯(cuò)。
可重入函數(shù)應(yīng)當(dāng)滿足條件:
- 不能含有靜態(tài)(全局)非常量數(shù)據(jù)。
- 不能返回靜態(tài)(全局)非常量數(shù)據(jù)的地址。
- 只能處理由調(diào)用者提供的數(shù)據(jù)。
- 不能依賴于單例模式資源的鎖。
- 調(diào)用(call)的函數(shù)也必需是可重入的。
可重入函數(shù)未必是線程安全的;線程安全函數(shù)未必是可重入的。
例子1:上述例子中的increment_counter函數(shù)是線程安全的,但是并不是可重入的。因?yàn)槭褂昧嘶コ怄i,如果這個(gè)函數(shù)用在可重入的中斷處理程序中,如果在pthread_mutex_lock(&mutex)和pthread_mutex_unlock(&mutex)之間產(chǎn)生另一個(gè)調(diào)用函數(shù)increment_counter的中斷,則會(huì)第二次執(zhí)行此函數(shù),此時(shí)由于mutex已被lock,函數(shù)會(huì)在pthread_mutex_lock(&mutex)處阻塞,并且由于mutex沒(méi)有機(jī)會(huì)被unlock,阻塞會(huì)永遠(yuǎn)持續(xù)下去。
例子2:一個(gè)函數(shù)打開(kāi)某個(gè)文件并讀入數(shù)據(jù)。這個(gè)函數(shù)是可重入的,因?yàn)樗亩鄠€(gè)實(shí)例同時(shí)執(zhí)行不會(huì)造成沖突;但它不是線程安全的,因?yàn)樵谒x入文件時(shí)可能有別的線程正在修改該文件,為了線程安全必須對(duì)文件加“同步鎖”。
-
編譯器
+關(guān)注
關(guān)注
1文章
1645瀏覽量
49475 -
C++語(yǔ)言
+關(guān)注
關(guān)注
0文章
147瀏覽量
7087 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21867
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
不同創(chuàng)建線程安全Set的方式
調(diào)用非安全線程的dll的問(wèn)題
XC32源碼和字符串線程安全
YYKit源碼線程安全計(jì)數(shù)器YYSentinel
Linux下的線程安全是什么
什么是線程安全?如何去實(shí)現(xiàn)線程安全?
請(qǐng)教大神rtthread中的ringbuff是線程安全的嗎
什么是線程安全
解決線程安全問(wèn)題技巧匯總
java的線程安全、單例模式、JVM內(nèi)存結(jié)構(gòu)
什么是線程安全 如何實(shí)現(xiàn)線程安全代碼
如何知道你的代碼是否線程安全

評(píng)論