單例模式與線程安全

目錄

線程安全和重?問題

死鎖和活鎖

死鎖

死鎖四個必要條件

活鎖

STL,智能指針和線程安全

線程安全的單例模式

餓漢模式

懶漢模式

懶漢模式實現單例模式(線程安全版本)

餓漢模式實現單例模式


我們來學習單例模式與線程安全

線程安全和重?問題

線程安全:就是多個線程在訪問共享資源時,能夠正確地執?,不會相互?擾或破壞彼此的執?結 果。?般??,多個線程并發同?段只有局部變量的代碼時,不會出現不同的結果。但是對全局變量 或者靜態變量進?操作,并且沒有鎖保護的情況下,容易出現該問題。

重?:同?個函數被不同的執?流調?,當前?個流程還沒有執?完,就有其他的執?流再次進?, 我們稱之為重?。?個函數在重?的情況下,運?結果不會出現任何不同或者任何問題,則該函數被 稱為可重?函數,否則,是不可重?函數。

學到現在,其實我們已經能理解重?其實可以分為兩種情況 :多線程重?函數 和信號導致?個執?流重復進?函數

所以上面這些都可以不用看的,直接輸出結論:

全局變量屬于臨界資源。

死鎖和活鎖

死鎖

死鎖是指在?組進程中的各個進程均占有不會釋放的資源,但因互相申請被其他進程所站?不會 釋放的資源?處于的?種永久等待狀態。

死鎖是指兩個或多個線程因為相互等待對方釋放資源,導致它們永遠無法繼續執行的狀態。

就是多個線程申請多個未釋放的鎖,然后他們各自卻持有這些鎖(未解鎖),導致都需要等待對方釋放,比如A線程持有鎖a,B線程持有鎖b,那B今天來申請鎖A,A申請鎖b,這樣A,B線程都需要各自等待對方釋放才能申請成功,跟那個shared_ptr的循環引用無法釋放一樣的道理。

死鎖本質上就是多個線程交叉持有對方需要的資源,但又都不釋放,導致所有線程都卡死在等待狀態。

只有一個線程申請鎖時也可能導致死鎖的,自己重復申請自己上次沒有釋放的鎖,怎么解決死鎖問題,只能使用外部外部干預,要干預首先了解死鎖四個必要條件。

死鎖四個必要條件

互斥條件:?個資源每次只能被?個執?流使?,如果多個執行流可以訪問同一個鎖,怎么可能會死鎖呢。破壞這個條件不太可能,違背鎖的意愿。

請求與保持條件:?個執?流因請求資源?阻塞時,對已獲得的資源保持不放。

不剝奪條件:?個執?流已獲得的資源,在末使?完之前,不能強?剝奪,就是一個線程使用鎖進入臨界區訪問具有原子性,還沒訪問完不能解鎖。

循環等待條件:若?執?流之間形成?種頭尾相接的循環等待資源的關系,導致死鎖完全是在鎖上等待導致的。打破這個簡單呀,使用try_lock進行申請鎖就可以了呀,鎖不可用就返回false。

所以如何避免死鎖就是破壞死鎖的四個必要條件中的一個就可以了,破壞循環等待條件問題:資源?次性分配,使?超時機制、加鎖順序?致,也可以避免鎖未釋放的場景。

活鎖

活鎖是指多個線程不斷地 相互讓步,但因為策略問題,導致它們無法取得進展,活鎖和死鎖的區別就是線程不會一直等待一個沒有解鎖的鎖,而是一直不斷的申請,直至申請成功。相當于這些線程一直在申請結果沒有成功,沒有成功還一直在申請。

由于不停的申請鎖會帶來CPU的高占用,所以也是程序禁止的一種情況。

STL,智能指針和線程安全

STL中的容器不一定都是線程安全的,大部分函數都不支持重入所以都是線程不安全的,STL的設計初衷是將性能挖掘到極致,??旦涉及到加鎖保證線程安全,會對性能造成巨?的影 響。?且對于不同的容器,加鎖?式的不同,性能可能也不同(例如hash表的鎖表和鎖桶)。因此STL默認不是線程安全,如果需要在多線程環境下使?,往往需要調?者??保證線程安全。

智能指針是線程安全的,所以我們才愿意將一個線程交給他管理,對于unique_ptr,由于只是在當前代碼塊范圍內?效,因此不涉及線程安全問題。

對于shared_ptr,多個對象需要共??個引?計數變量,所以會存在線程安全問題。但是標準庫實現的時 候考慮到了這個問題,基于原?操作(CAS)的?式保證shared_ptr能夠?效,原?的操作引?計數。

但是智能指針指向的對象不一定是線程安全的,只是智能指針的操作的線程安全的。

線程安全的單例模式

某些類,只應該具有?個對象(實例),就稱之為單例。?例如?個男?只能有?個媳婦。?在很多服務器開發場景中,經常需要讓服務器加載很多的數據(上百G)到內存中,此時往往要??個單例 的類來管理這些數據。

單例模式主要分為餓漢模式和懶漢模式。要實現單例那這兩種模式都是禁止拷貝的,而且初始化函數必須放為私有,不讓外部直接通過構造函數訪問。

餓漢模式

吃完飯, ?刻洗碗, 這種就是餓漢?式.,因為下?頓吃的時候可以?刻拿著碗就能吃飯。也就是餓漢模式里面肯定已經創建出了一個靜態的對象,等你要調用的時候,通過一個static函數返回,保證返回函數,和創建的靜態變量在類中只有一份,防止拷貝。

懶漢模式

吃完飯, 先把碗放下, 然后下?頓飯?到這個碗了再洗碗, 就是懶漢?式,就是需要時才創建對象的機制,屬于延遲創建/加載,同樣也只有一個對象,所以new出來的對象需要裝在一個static指針里面,而這個指針是類里面已經創建好的。

反正需要時才調用getinstance創建,然后返回。存在?個嚴重的問題,線程不安全。?第?次調?GetInstance的時候,如果兩個線程同時調?,可能會創建出兩份T對象的實例,?但是后續再次調?,就沒有問題了,此時inst就不是空了,不能創建對象了。

懶漢模式實現單例模式(線程安全版本)

#pragma once
#include<iostream>
using namespace std;
#include<memory>
#include<unistd.h>
#include<vector>
#include<string>
#include<queue>
#include<functional> //
#include"pthread.hpp"
#include"mutex.hpp"
#include"cond.hpp"
#include"log.hpp"namespace threadpoolmodule
{using namespace lockmodule;using namespace threadmodule;using namespace condmodule;using namespace logmodule;using thread_t = shared_ptr<thread>;//用來做測試的線程方法void defaulttest(){while (true){LOG(loglevel::DEBUG) << "我是一個測試方法";sleep(1);}}const static int defaultnum = 5;template<class T>class threadpool{private:bool isempty(){return _taskq.empty();}void HandleTask(string name){LOG(loglevel::INFO) << "線程進入HandleTask的邏輯\n";while (true){T t;{lockguard lockguard(mut);// 1.拿任務while (isempty() && _isrunning){_wait_num++;_cond.wait(mut);_wait_num--;}if (isempty() && !_isrunning){break;}t = _taskq.front();_taskq.pop();}//2 處理任務t(name); //規定,未來所有的任務處理都是必須提供()直接調用的方法}LOG(loglevel::INFO) << "線程:" << name << "退出";}private:threadpool(): _num(defaultnum), _wait_num(0),_isrunning(false){for (int i = 0; i < _num; i++){//_threads.push_back(make_shared<thread>(bind(&threadpool::HandleTask, this, placeholders::_1)));LOG(loglevel::INFO) << "構鍵線程" << _threads.back()->getname() << "對象 。。。成功";}}public:threadpool<T>& operator=(const threadpool<T>&) = delete; //使用operator=的賦值拷貝不能用了threadpool(const threadpool<T>&) = delete;  //賦值拷貝不能用了static threadpool<T>* getinstance(){LOG(loglevel::INFO) << "單例首次被執行,需要加載對象...";if (instance == nullptr){instance = new threadpool<T>();}return instance;}void start(){if (_isrunning){return;}_isrunning = true;for (auto& thread_ptr : _threads){thread_ptr->start();LOG(loglevel::INFO) << "啟動線程" << thread_ptr->getname() << "。。。成功";}}void equeue(T in){lockguard lockguard(mut);if (!_isrunning){return;}_taskq.push(move(in));if ( _wait_num){_cond.notify();}}void stop(){//讓里面的線程都重新自己退出//退出之前需要將所有的任務都處理完,所以需要一次性喚醒所有在等待的線程if (_isrunning){_isrunning = false;if (_wait_num){_cond.notifyall();}}}void join(){for (auto& thread_ptr : _threads){thread_ptr->join();LOG(loglevel::INFO) << "停止線程" << thread_ptr->getname() << "。。。成功";}}~threadpool(){}private:vector<thread_t> _threads;  //線程池中的線程初始個數在數組里面int _num;   //線程池的容量queue<T> _taskq;  //線程池中的任務隊列mutex mut;cond _cond;int _wait_num; //等待隊列線程bool _isrunning;static threadpool<T>* instance;};template<class T>threadpool<T>* threadpool<T>::instance = nullptr; //在外面初始化, 私有的static成員可以在類外初始化
};

instance必須放私有,這樣比較安全。

懶漢模型這種延遲生成,按需生成的技術在操作系統內核用的還是比較多的,像什么寫時拷貝,malloc創建空間,物理地址的映射都是。

“不到萬不得已,絕不提前分配”,這種按需分配的策略在操作系統內核中極為常見,既能提升性能,又能節省資源。

但是外面的getinstance方法還沒有加鎖保護,所以需要多增加一個鎖進行保護。進入函數的時候立即加鎖,加鎖前需要先判斷是否滿足instance是空的,不然白加鎖了,為什么使用雙重判斷呢,外層是為了防止白加鎖,內層是為了防止加鎖失敗或者其他原因導致多個線程進來從而導致instance被賦值兩次。

靜態成員函數不能直接訪問類的非靜態成員變量,所以這個鎖也應該是靜態的才可以被訪問到。

private:vector<thread_t> _threads;  //線程池中的線程初始個數在數組里面int _num;   //線程池的容量queue<T> _taskq;  //線程池中的任務隊列mutex mut;static mutex _lock;cond _cond;int _wait_num; //等待隊列線程bool _isrunning;static threadpool<T>* instance;};template<class T>threadpool<T>* threadpool<T>::instance = nullptr; //在外面初始化, 私有的static成員可以在類外初始化template<class T>mutex threadpool<T>::_lock;

餓漢模式實現單例模式

#pragma once
#include<iostream>
using namespace std;
#include<memory>
#include<unistd.h>
#include<vector>
#include<string>
#include<queue>
#include<functional> //
#include"pthread.hpp"
#include"mutex.hpp"
#include"cond.hpp"
#include"log.hpp"namespace threadpoolmodule
{using namespace lockmodule;using namespace threadmodule;using namespace condmodule;using namespace logmodule;using thread_t = shared_ptr<thread>;//用來做測試的線程方法void defaulttest(){while (true){LOG(loglevel::DEBUG) << "我是一個測試方法";sleep(1);}}const static int defaultnum = 5;template<class T>class threadpool{private:bool isempty(){return _taskq.empty();}void HandleTask(string name){LOG(loglevel::INFO) << "線程進入HandleTask的邏輯\n";while (true){T t;{lockguard lockguard(mut);// 1.拿任務while (isempty() && _isrunning){_wait_num++;_cond.wait(mut);_wait_num--;}if (isempty() && !_isrunning){break;}t = _taskq.front();_taskq.pop();}//2 處理任務t(name); //規定,未來所有的任務處理都是必須提供()直接調用的方法}LOG(loglevel::INFO) << "線程:" << name << "退出";}private:threadpool(): _num(defaultnum), _wait_num(0),_isrunning(false){for (int i = 0; i < _num; i++){//_threads.push_back(make_shared<thread>(bind(&threadpool::HandleTask, this, placeholders::_1)));LOG(loglevel::INFO) << "構鍵線程" << _threads.back()->getname() << "對象 。。。成功";}}public:threadpool<T>& operator=(const threadpool<T>&) = delete; //使用operator=的賦值拷貝不能用了threadpool(const threadpool<T>&) = delete;  //賦值拷貝不能用了static threadpool<T>* getinstance(){LOG(loglevel::INFO) << "單例首次被執行,需要加載對象...";return &instance;}void start(){if (_isrunning){return;}_isrunning = true;for (auto& thread_ptr : _threads){thread_ptr->start();LOG(loglevel::INFO) << "啟動線程" << thread_ptr->getname() << "。。。成功";}}void equeue(T in){lockguard lockguard(mut);if (!_isrunning){return;}_taskq.push(move(in));if ( _wait_num){_cond.notify();}}void stop(){//讓里面的線程都重新自己退出//退出之前需要將所有的任務都處理完,所以需要一次性喚醒所有在等待的線程if (_isrunning){_isrunning = false;if (_wait_num){_cond.notifyall();}}}void join(){for (auto& thread_ptr : _threads){thread_ptr->join();LOG(loglevel::INFO) << "停止線程" << thread_ptr->getname() << "。。。成功";}}~threadpool(){}private:vector<thread_t> _threads;  //線程池中的線程初始個數在數組里面int _num;   //線程池的容量queue<T> _taskq;  //線程池中的任務隊列mutex mut;cond _cond;int _wait_num; //等待隊列線程bool _isrunning;static threadpool<T> instance;};template<class T>threadpool<T> threadpool<T>::instance; //在外面初始化, 私有的static成員可以在類外初始化
};

這個實現起來差不多呀,而且完全沒有線程安全問題,因為就一個對象。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899922.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899922.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899922.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python+AI提示詞用貝葉斯樣條回歸擬合BSF方法分析櫻花花期數據模型構建跡圖、森林圖可視化

原文鏈接&#xff1a;https://tecdat.cn/?p41308 在數據科學的領域中&#xff0c;我們常常會遇到需要處理復雜關系的數據。在眾多的數據分析方法中&#xff0c;樣條擬合是一種非常有效的處理數據非線性關系的手段。本專題合集圍繞如何使用PyMC軟件&#xff0c;對櫻花花期數據進…

WPF學習路線

WPF學習路線 學習準備學習技術棧學習路線 1-5&#xff08;1-2周&#xff09;6-8&#xff08;3-5周&#xff09; 學習準備 個人認為前端技術一般幾個關鍵字&#xff1a;元素資源 控制元素資源組合或者動態交互 數據交互呈現分析關鍵字得到的就是幾個方向 布局 樣式 組裝資源控件…

31天Python入門——第20天:魔法方法詳解

你好&#xff0c;我是安然無虞。 文章目錄 魔法方法1. __new__和__del__2. __repr__和__len__3. __enter__和__exit__4. 可迭代對象和迭代器5. 中括號[]數據操作6. __getattr__、__setattr__ 和 __delattr__7. 可調用的8. 運算符 魔法方法 魔法方法: Python中的魔法方法是一類…

棧 —— 數據結構基礎刷題路程

一、P1739 表達式括號匹配 - 洛谷 算法代碼&#xff1a; #include<bits/stdc.h> using namespace std; const int N300008; struct mystack {int a[N];int t-1;//壓棧void push(int data){a[t]data; } //取棧頂元素int top(){return a[t]; } //彈出棧頂元素void pop(){i…

瑞昱RTD2556QR顯示器驅動芯片

一、概述 RTD2556QR芯片是由Realtek公司精心研發的一款高性能顯示驅動芯片&#xff0c;專為滿足現代顯示設備對高分辨率、多功能接口及穩定性能的需求而設計。該芯片憑借其卓越的技術特性和廣泛的應用領域&#xff0c;在顯示驅動市場中占據重要地位。它集成了多種先進的功能模…

PyQt5和OpenCV車牌識別系統

有需要請加文章底部Q哦 可遠程調試 PyQt5和OpenCV車牌識別系統 一 介紹 此車牌識別系統基于PyQt5和OpenCV開發&#xff0c;藍牌&#xff0c;新能源(綠牌)&#xff0c;黃牌&#xff0c;白牌均可以準確識別&#xff0c;支持中文識別&#xff0c;可以導出識別結果(Excel格式)。此…

學有所記- 探索FastAPI在docker上的部署

目標&#xff1a; 學習怎樣在docker中安裝部署FastAPI&#xff0c;完成項目結構的搭建以及hello world的運行 背景&#xff1a; 公司內服務器資源有限&#xff0c;為了共享算力資源&#xff0c;同時又能隔離運行環境&#xff0c;因此采用了docker部署的方式&#xff0c;進行各…

HTTP keepalive 詳解

一、簡介 HTTP協議早期版本&#xff0c;比如1.0&#xff0c;默認是不使用持久連接的&#xff0c;也就是每個請求/響應之后都會關閉TCP連接。這樣的話&#xff0c;每次請求都需要重新建立連接&#xff0c;增加了延遲和資源消耗。Keep-Alive的作用是保持連接&#xff0c;讓多個請…

長短期記憶神經網絡(LSTM)基礎學習與實例:預測序列的未來

目錄 1. 前言 2. LSTM的基本原理 2.1 LSTM基本結構 2.2 LSTM的計算過程 3. LSTM實例&#xff1a;預測序列的未來 3.1 數據準備 3.2 模型構建 3.3 模型訓練 3.4 模型預測 3.5 完整程序預測序列的未來 4. 總結 1. 前言 在深度學習領域&#xff0c;循環神經網絡&…

基于機器學習的三國時期諸葛亮北伐失敗因素量化分析

一、研究背景與方法論 1.1 歷史問題的數據化挑戰 三國時期&#xff08;220-280年&#xff09;的戰爭史存在史料分散、數據缺失的特點。本研究通過構建包含軍事、經濟、地理、政治四大維度的結構化數據庫&#xff0c;收錄建安十二年&#xff08;207年&#xff09;至建興十二年…

藍橋杯省模擬賽 數位和

問題描述 只能被 1 和本身整除的數稱為質數。 請問在 1 &#xff08;含&#xff09;到 1000000 &#xff08;含&#xff09;中&#xff0c;有多少個質數的各個數位上的數字之和為 23 。 提示&#xff1a;599 就是這樣一個質數&#xff0c;各個數位上的數字之和為 59923 。 #…

Timer的底層實現原理?

Timer 是 Java 中用于定時任務調度的基礎工具類,其底層實現基于 單線程+任務隊列 的模型。以下是 Timer 的底層實現原理的詳細分析: 一、核心組件 TimerThread 繼承自 Thread,是 Timer 的工作線程,負責從隊列中提取任務并執行。通過 while (true) 循環持續檢查任務隊列。Ta…

Java 枚舉類 Key-Value 映射的幾種實現方式及最佳實踐

Java 枚舉類 Key-Value 映射的幾種實現方式及最佳實踐 前言 在 Java 開發中&#xff0c;枚舉(Enum)是一種特殊的類&#xff0c;它能夠定義一組固定的常量。在實際應用中&#xff0c;我們經常需要為枚舉常量添加額外的屬性&#xff0c;并實現 key-value 的映射關系。本文將詳細…

青少年編程與數學 02-015 大學數學知識點 01課題、概要

青少年編程與數學 02-015 大學數學知識點 01課題、概要 一、線性代數二、概率論與數理統計三、微積分四、優化理論五、離散數學六、數值分析七、信息論 《青少年編程與數學》課程要求&#xff0c;在高中畢業前&#xff0c;盡量完成大部分大學數學知識的學習。一般可以通過線上課…

智能打印預約系統:微信小程序+SSM框架實戰項目

微信小程序打印室預約系統&#xff0c;采用SSM&#xff08;SpringSpringMVCMyBatis&#xff09;經典框架組合。 一、系統核心功能詳解 1. 智能化管理后臺 ?用戶數據看板?打印店資源管理?預約動態監控?服務評價系統 2. 微信小程序端 ?智能定位服務?預約時段選擇?文件…

DataX 3.0 實戰案例

第五章 實戰案例 5.1. 案例一 5.1.1. 案例介紹 MySQL數據庫中有兩張表&#xff1a;用戶表(users)&#xff0c;訂單表(orders)。其中用戶表中存儲的是所有的用戶的信息&#xff0c;訂單表中存儲的是所有的訂單的信息。表結構如下&#xff1a; 用戶表 users: id&#xff1a;用…

設計模式學習(1)

面向對象設計原則 單一職責 每個類只有一個職責&#xff0c;并被完整的封裝在類中&#xff0c;該原則用來控制類的粒度。 例如Mapper&#xff0c;controller都只負責一個業務。 開閉原則 應該對擴展開放&#xff0c;而對修改封閉&#xff0c;例如定義接口或是抽象類作為抽…

在 Rocky Linux 9.2 上編譯安裝 Redis 6.2.6

文章目錄 在 Rocky Linux 9.2 上編譯安裝 Redis 6.2.6Redis 介紹官網Redis 的核心特性高性能支持多種數據結構多種持久化機制復制與高可用2.5 事務與 Lua 腳本消息隊列功能 Redis 適用場景Redis 與其他數據庫對比Redis 的優勢與劣勢Redis 優勢Redis 劣勢 部署過程系統環境信息環…

量子計算與經典計算的融合與未來

最近研學過程中發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊下方超鏈接跳轉到網站人工智能及編程語言學習教程。讀者們可以通過里面的文章詳細了解一下人工智能及其編程等教程和學習方法。下面進入文章正…

數據結構(4)——帶哨兵位循環雙向鏈表

目錄 前言 一、帶哨兵的循環雙向鏈表是什么 二、鏈表的實現 2.1規定結構體 2.2創建節點 2.3初始化 2.4打印 2.5檢驗是否為空 2.6銷毀鏈表 2.7尾插 2.8尾刪 2.9頭插 2.10頭刪 2.11尋找特定節點 2.12任意位置插入&#xff08;pos前&#xff09; 2.13刪除任意節點 …