重慶模板網(wǎng)站多少錢免費網(wǎng)站制作教程
一、線程安全的單例模式
什么是單例模式
單例模式是一種“經(jīng)典的,常用的,??嫉摹?strong>設(shè)計模式
什么是設(shè)計模式
IT行業(yè)這么火,涌入的人很多.俗話說林子大了啥鳥都有。大佬和菜雞們兩極分化的越來越嚴(yán)重,為了讓菜雞們不太拖大佬的后腿,于是大佬們針對一些經(jīng)典的常見的場景,給定了一些對應(yīng)的解決方案,這個就是 設(shè)計模式
單例模式的特點
某些類,只應(yīng)該具有一個對象(實例),就稱之為單例
例如一個男人只能有一個媳婦
在很多服務(wù)器開發(fā)場景中,經(jīng)常需要讓服務(wù)器加載很多的數(shù)據(jù)(上百G)到內(nèi)存中,此時往往要用一個單例的類來管理這些數(shù)據(jù)
餓漢實現(xiàn)方式和懶漢實現(xiàn)方式
【洗碗的例子】
吃完飯,立刻洗碗,這種就是餓漢方式.因為下一頓吃的時候可以立刻拿著碗就能 吃完飯,先把碗放下,然后下一頓飯用到這個碗了再洗碗,就是懶漢方式?
懶漢方式最核心的思想是“延時加載”,從而能夠優(yōu)化服務(wù)器的啟動速度
餓漢方式實現(xiàn)單例模式
懶漢方式實現(xiàn)單例模式
存在一個嚴(yán)重的問題,線程不安全
第一次調(diào)用 GetInstance的時候,如果兩個線程同時調(diào)用,可能會創(chuàng)建出兩份T對象的實例
但是后續(xù)再次調(diào)用,就沒有問題了
懶漢方式實現(xiàn)單例模式(線程安全版本)
// 懶漢模式, 線程安全
template <typename T>
class Singleton {volatile static T* inst; // 需要設(shè)置 volatile 關(guān)鍵字, 否則可能被編譯器優(yōu)化.static std::mutex lock;
public:static T* GetInstance() {if (inst == NULL) { // 雙重判定空指針, 降低鎖沖突的概率, 提高性能.lock.lock(); // 使用互斥鎖, 保證多線程情況下也只調(diào)用一次 new.if (inst == NULL) {inst = new T();}lock.unlock();
}return inst;}
};
注意事項:
-
加鎖解鎖的位置
-
雙重 if 判定, 避免不必要的鎖競爭
-
volatile關(guān)鍵字防止過度優(yōu)化
二、STL,智能指針和線程安全
STL 中的容器是否是線程安全的?
不是. 原因是,STL的設(shè)計初衷是將性能挖掘到極致,而一旦涉及到加鎖保證線程安全,會對性能造成巨大的影響. 而且對于不同的容器,加鎖方式的不同,性能可能也不同(例如hash 表的鎖表和鎖桶).因此 STL 默認(rèn)不是線程安全.如果需要在多線程環(huán)境下使用,往往需要調(diào)用者自行保證線程安全.
智能指針是否是線程安全的?
對于 unique_ptr,由于只是在當(dāng)前代碼塊范圍內(nèi)生效,因此不涉及線程安全問題.
對于 shared_ptr,多個對象需要共用一個引用計數(shù)變量,所以會存在線程安全問題.但是標(biāo)準(zhǔn)庫實現(xiàn)的時候考慮到了這個問題,基于原子操作(CAS)的方式保證 shared_ptr 能夠高效,原子的操作引用計數(shù).
★ps:智能指針不等于智能指針對象,所以在應(yīng)用中該加鎖加鎖
三、可重入 VS 線程安全
概念
-
線程安全
多個線程并發(fā)同一段代碼時,不會出現(xiàn)不同的結(jié)果。常見對全局變量或者靜態(tài)變量進(jìn)行操作,并且沒有鎖保護(hù)的情況下,會出現(xiàn)該問題
-
重入
同一個函數(shù)被不同的執(zhí)行流調(diào)用,當(dāng)前一個流程還沒有執(zhí)行完,就有其它的執(zhí)行流再次進(jìn)入,我們稱之為重入。一個函數(shù)在重入的情況下,運行結(jié)果不會出現(xiàn)任何不同或者任何問題,則該函數(shù)被稱為可重入函數(shù),否則,是不可重入函數(shù)
★ps:可重入不可重入函數(shù)表示能不能被多個執(zhí)行流同時進(jìn)入
★ps:線程安全表示多線程并發(fā)執(zhí)行一段代碼會不會出錯
常見的線程不安全的情況
? 不保護(hù)共享變量的函數(shù)
? 函數(shù)狀態(tài)隨著被調(diào)用,狀態(tài)發(fā)生變化的函數(shù)
? 返回指向靜態(tài)變量指針的函數(shù)
? 調(diào)用線程不安全函數(shù)的函數(shù)
常見的線程安全的情況
? 每個線程對全局變量或者靜態(tài)變量只有讀取的權(quán)限,而沒有寫入的權(quán)限,一般來說這些線程是安全的
? 類或者接口對于線程來說都是原子操作
? 多個線程之間的切換不會導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性
常見不可重入的情況
? 調(diào)用了 malloc/free 函數(shù),因為 malloc 函數(shù)是用全局鏈表來管理堆的
? 調(diào)用了標(biāo)準(zhǔn)I/O庫函數(shù),標(biāo)準(zhǔn)I/O庫的很多實現(xiàn)都以不可重入的方式使用全局?jǐn)?shù)據(jù)結(jié)構(gòu)
? 可重入函數(shù)體內(nèi)使用了靜態(tài)的數(shù)據(jù)結(jié)構(gòu)
常見可重入的情況
? 不使用全局變量或者靜態(tài)變量
? 不使用malloc 或者 new 開辟出的空間
? 不調(diào)用不可重入函數(shù)
? 不返回靜態(tài)或全局?jǐn)?shù)據(jù),所有數(shù)據(jù)都有函數(shù)的調(diào)用者提供
? 使用本地數(shù)據(jù),或者通過制作全局?jǐn)?shù)據(jù)的本地拷貝來保護(hù)全局?jǐn)?shù)據(jù)
可重入與線程安全聯(lián)系
? 函數(shù)是可重入的,那線程就是安全的
? 函數(shù)是不可重入的,那就不能由多個線程使用,有可能引發(fā)線程安全問題
? 如果一個函數(shù)中有全局變量,那么這個函數(shù)既不是線程安全也不是可重入的
可重入與線程安全的區(qū)別
? 可重入函數(shù)是線程安全函數(shù)的一種
? 線程安全不一定是可重入的,而可重入函數(shù)則一定是安全的
? 如果將對臨界資源的訪問加上鎖,則這個函數(shù)是線程安全的,但如果這個重入函數(shù)若鎖還未釋放則會產(chǎn)生死鎖,因此是不可重入的
四、死鎖
概念
死鎖是指在一組進(jìn)程中的各個進(jìn)程均占有不會釋放的資源,但因互相申請被其他進(jìn)程所站用 不會釋放的資源而處于的一種永久等待狀態(tài)。
死鎖的四個必要條件
? 互斥條件:一個資源每次只能被一個執(zhí)行流使用 ? 請求與保持條件:一個執(zhí)行流因請求資源而阻塞時,對已獲得的資源保持不放 ? 不剝奪條件:一個執(zhí)行流已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪 ? 循環(huán)等待條件:若干執(zhí)行流之間形成一種頭尾相接的循環(huán)等待資源的關(guān)系
避免死鎖
? 破壞死鎖的四個必要條件
? 加鎖順序一致
? 避免鎖未釋放的場景
? 資源一次性分配
避免死鎖的算法
死鎖檢測算法
銀行家算法