C++ 线程安全的单例模式总结
生活随笔
收集整理的這篇文章主要介紹了
C++ 线程安全的单例模式总结
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
什么是線程安全?
在擁有共享數據的多條線程并行執行的程序中,線程安全的代碼會通過同步機制保證各個線程都可以正常且正確的執行,不會出現數據污染等意外情況。
如何保證線程安全?
什么是單例模式?
單例模式指在整個系統生命周期里,保證一個類只能產生一個實例,確保該類的唯一性。
單例模式分類
單例模式可以分為懶漢式和餓漢式,兩者之間的區別在于創建實例的時間不同:
- 懶漢式:指系統運行中,實例并不存在,只有當需要使用該實例時,才會去創建并使用實例。(這種方式要考慮線程安全)
- 餓漢式:指系統一運行,就初始化創建實例,當需要時,直接調用即可。(本身就線程安全,沒有多線程的問題)
單例類特點
- 構造函數和析構函數為private類型,目的禁止外部構造和析構
- 拷貝構造和賦值構造函數為private類型,目的是禁止外部拷貝和賦值,確保實例的唯一性
- 類里有個獲取實例的靜態函數,可以全局訪問
01 普通懶漢式單例 ( 線程不安全 )
/// 普通懶漢式實現 -- 線程不安全 // #include <iostream> // std::cout #include <mutex> // std::mutex #include <pthread.h> // pthread_createclass SingleInstance {public:// 獲取單例對象static SingleInstance *GetInstance();// 釋放單例,進程退出時調用static void deleteInstance();// 打印單例地址void Print();private:// 將其構造和析構成為私有的, 禁止外部構造和析構SingleInstance();~SingleInstance();// 將其拷貝構造和賦值構造成為私有函數, 禁止外部拷貝和賦值SingleInstance(const SingleInstance &signal);const SingleInstance &operator=(const SingleInstance &signal);private:// 唯一單例對象指針static SingleInstance *m_SingleInstance; };//初始化靜態成員變量 SingleInstance *SingleInstance::m_SingleInstance = NULL;SingleInstance* SingleInstance::GetInstance() {if (m_SingleInstance == NULL){m_SingleInstance = new (std::nothrow) SingleInstance; // 沒有加鎖是線程不安全的,當線程并發時會創建多個實例}return m_SingleInstance; }void SingleInstance::deleteInstance() {if (m_SingleInstance){delete m_SingleInstance;m_SingleInstance = NULL;} }void SingleInstance::Print() {std::cout << "我的實例內存地址是:" << this << std::endl; }SingleInstance::SingleInstance() {std::cout << "構造函數" << std::endl; }SingleInstance::~SingleInstance() {std::cout << "析構函數" << std::endl; } /// 普通懶漢式實現 -- 線程不安全 //// 線程函數 void *PrintHello(void *threadid) {// 主線程與子線程分離,兩者相互不干涉,子線程結束同時子線程的資源自動回收pthread_detach(pthread_self());// 對傳入的參數進行強制類型轉換,由無類型指針變為整形數指針,然后再讀取int tid = *((int *)threadid);std::cout << "Hi, 我是線程 ID:[" << tid << "]" << std::endl;// 打印實例地址SingleInstance::GetInstance()->Print();pthread_exit(NULL); }#define NUM_THREADS 5 // 線程個數int main(void) {pthread_t threads[NUM_THREADS] = {0};int indexes[NUM_THREADS] = {0}; // 用數組來保存i的值int ret = 0;int i = 0;std::cout << "main() : 開始 ... " << std::endl;for (i = 0; i < NUM_THREADS; i++){std::cout << "main() : 創建線程:[" << i << "]" << std::endl;indexes[i] = i; //先保存i的值// 傳入的時候必須強制轉換為void* 類型,即無類型指針ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));if (ret){std::cout << "Error:無法創建線程," << ret << std::endl;exit(-1);}}// 手動釋放單實例的資源SingleInstance::deleteInstance();std::cout << "main() : 結束! " << std::endl;return 0; }普通懶漢式單例運行結果:
從運行結果可知,單例構造函數創建了兩個,內存地址分別為0x7f3c980008c0和0x7f3c900008c0,所以普通懶漢式單例只適合單進程不適合多線程,因為是線程不安全的。
02 加鎖的懶漢式單例 ( 線程安全 )
/// 加鎖的懶漢式實現 // class SingleInstance {public:// 獲取單實例對象static SingleInstance *&GetInstance();//釋放單實例,進程退出時調用static void deleteInstance();// 打印實例地址void Print();private:// 將其構造和析構成為私有的, 禁止外部構造和析構SingleInstance();~SingleInstance();// 將其拷貝構造和賦值構造成為私有函數, 禁止外部拷貝和賦值SingleInstance(const SingleInstance &signal);const SingleInstance &operator=(const SingleInstance &signal);private:// 唯一單實例對象指針static SingleInstance *m_SingleInstance;static std::mutex m_Mutex; };//初始化靜態成員變量 SingleInstance *SingleInstance::m_SingleInstance = NULL; std::mutex SingleInstance::m_Mutex;SingleInstance *&SingleInstance::GetInstance() {// 這里使用了兩個 if判斷語句的技術稱為雙檢鎖;好處是,只有判斷指針為空的時候才加鎖,// 避免每次調用 GetInstance的方法都加鎖,鎖的開銷畢竟還是有點大的。if (m_SingleInstance == NULL) {std::unique_lock<std::mutex> lock(m_Mutex); // 加鎖if (m_SingleInstance == NULL){m_SingleInstance = new (std::nothrow) SingleInstance;}}return m_SingleInstance; }void SingleInstance::deleteInstance() {std::unique_lock<std::mutex> lock(m_Mutex); // 加鎖if (m_SingleInstance){delete m_SingleInstance;m_SingleInstance = NULL;} }void SingleInstance::Print() {std::cout << "我的實例內存地址是:" << this << std::endl; }SingleInstance::SingleInstance() {std::cout << "構造函數" << std::endl; }SingleInstance::~SingleInstance() {std::cout << "析構函數" << std::endl; } /// 加鎖的懶漢式實現 //加鎖的懶漢式單例的運行結果:
從運行結果可知,只創建了一個實例,內存地址是0x7f28b00008c0,所以加了互斥鎖的普通懶漢式是線程安全的
03 內部靜態變量的懶漢單例(C++11 線程安全)
/// 內部靜態變量的懶漢實現 // class Single {public:// 獲取單實例對象static Single &GetInstance();// 打印實例地址void Print();private:// 禁止外部構造Single();// 禁止外部析構~Single();// 禁止外部復制構造Single(const Single &signal);// 禁止外部賦值操作const Single &operator=(const Single &signal); };Single &Single::GetInstance() {// 局部靜態特性的方式實現單實例static Single signal;return signal; }void Single::Print() {std::cout << "我的實例內存地址是:" << this << std::endl; }Single::Single() {std::cout << "構造函數" << std::endl; }Single::~Single() {std::cout << "析構函數" << std::endl; } /// 內部靜態變量的懶漢實現 //內部靜態變量的懶漢單例的運行結果:
-std=c++0x編譯是使用了C++11的特性,在C++11內部靜態變量的方式里是線程安全的,只創建了一次實例,內存地址是0x6016e8,這個方式非常推薦,實現的代碼最少!
[root@lincoding singleInstall]#g++ SingleInstance.cpp -o SingleInstance -lpthread -std=c++0x04 餓漢式單例 (本身就線程安全)
// 餓漢實現 / class Singleton { public:// 獲取單實例static Singleton* GetInstance();// 釋放單實例,進程退出時調用static void deleteInstance();// 打印實例地址void Print();private:// 將其構造和析構成為私有的, 禁止外部構造和析構Singleton();~Singleton();// 將其拷貝構造和賦值構造成為私有函數, 禁止外部拷貝和賦值Singleton(const Singleton &signal);const Singleton &operator=(const Singleton &signal);private:// 唯一單實例對象指針static Singleton *g_pSingleton; };// 代碼一運行就初始化創建實例 ,本身就線程安全 Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;Singleton* Singleton::GetInstance() {return g_pSingleton; }void Singleton::deleteInstance() {if (g_pSingleton){delete g_pSingleton;g_pSingleton = NULL;} }void Singleton::Print() {std::cout << "我的實例內存地址是:" << this << std::endl; }Singleton::Singleton() {std::cout << "構造函數" << std::endl; }Singleton::~Singleton() {std::cout << "析構函數" << std::endl; } // 餓漢實現 /餓漢式單例的運行結果:
從運行結果可知,餓漢式在程序一開始就構造函數初始化了,所以本身就線程安全的
總結
以上是生活随笔為你收集整理的C++ 线程安全的单例模式总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 需求分析模板
- 下一篇: 解决Firefox已阻止运行早期版本Ad