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