C++线程安全单例类最全总结
生活随笔
收集整理的這篇文章主要介紹了
C++线程安全单例类最全总结
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
#include <thread>
#include <iostream>
#include <mutex>
// 最原始的單例模式的寫法,不是線程安全的,并且會(huì)內(nèi)存泄漏。
// 線程不安全的原因:假設(shè)有兩個(gè)線程都執(zhí)行g(shù)etInstance函數(shù)。當(dāng)線程1調(diào)用singleton = new Singleton1()
// 語句時(shí)候,操作系統(tǒng)系統(tǒng)突然切換到線程2,線程2判斷if (singleton == nullptr)生效,線程2執(zhí)行
// singleton = new Singleton1();當(dāng)線程2執(zhí)行完后,singleton已經(jīng)生成。然后切換到線程1,線程1繼續(xù)執(zhí)行
// singleton = new Singleton1(),singleton會(huì)再次生成。這不符合單例設(shè)計(jì)的原則。
// 內(nèi)存泄漏的原因:析構(gòu)函數(shù)沒法調(diào)用,所以無法通過析構(gòu)函數(shù)調(diào)用delete,刪除singleton內(nèi)存class Singleton1
{
public:~Singleton1() {std::cout << "Singleton1析構(gòu)函數(shù)調(diào)用" << std::endl;} // 析構(gòu)函數(shù)其實(shí)不會(huì)調(diào)用,所以new出來的靜態(tài)成員變量會(huì)內(nèi)存泄漏。static Singleton1* getInstance(){if (singleton == nullptr){singleton = new Singleton1();}return singleton;}void func(){printf("調(diào)用func函數(shù)\n");}
private:// static函數(shù)只能調(diào)用靜態(tài)成員變量或者靜態(tài)函數(shù),所以下面這個(gè)靜態(tài)成員變量必須為static static Singleton1* singleton;Singleton1(){}
};
// 靜態(tài)非const整形成員變量必須在類外定義
Singleton1* Singleton1::singleton = nullptr;// 再寫個(gè)單例模式,采用類中類解決內(nèi)存泄露的問題。其實(shí)在main函數(shù)中也可以手動(dòng)delete,只不過不是很優(yōu)雅。
class Singleton2
{
private:Singleton2(){}static Singleton2* singleton;
public:~Singleton2() {std::cout << "Singleton2析構(gòu)函數(shù)調(diào)用" << std::endl;} // 析構(gòu)函數(shù)其實(shí)不會(huì)調(diào)用,所以new出來的靜態(tài)成員變量會(huì)內(nèi)存泄漏。static Singleton2* getInstance(){if (singleton == nullptr){singleton = new Singleton2();static PtrCycle ptr; // C++能確保靜態(tài)變量只能被初始化一次,不會(huì)因?yàn)檎{(diào)用getInstance,多次創(chuàng)建靜態(tài)對(duì)象。}return singleton;}void func(){printf("調(diào)用func函數(shù)\n");}private:class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; //這里會(huì)調(diào)用析構(gòu)函數(shù)singleton = nullptr;std::cout << "釋放內(nèi)存" << std::endl;}}};
};
Singleton2* Singleton2::singleton = nullptr; //必須要在類外初始化// 上面的寫法還是線程不安全的。為了解決線程安全,引申出下面的餓漢式和懶漢式寫法。
// 餓漢式:一開始就初始化單例對(duì)象
// 餓漢式寫法一:把對(duì)象用new放在堆上。
class Singleton3
{
private:static Singleton3* singleton;Singleton3(){}
public:void func(){printf("調(diào)用func函數(shù)\n");}static Singleton3* getInstance(){static PtrCycle ptr;return singleton;}~Singleton3(){std::cout << "Singleton3析構(gòu)函數(shù)" << std::endl;}
private:class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; // 這里會(huì)調(diào)用析構(gòu)函數(shù)singleton = nullptr;std::cout << "釋放內(nèi)存" << std::endl;}}};
};
Singleton3* Singleton3::singleton = new Singleton3(); //靜態(tài)對(duì)象類外初始化,其實(shí)這個(gè)寫法不好
// 上面new出來的這個(gè)指針,如果getInstace函數(shù)從沒被調(diào)用過,那么因?yàn)閚ew Singleton3()
// 得到的內(nèi)存從沒被釋放,會(huì)發(fā)生內(nèi)存泄漏。// 餓漢式寫法二:把對(duì)象放在靜態(tài)區(qū),不使用new。
// 這種寫法不需要寫類中類去釋放內(nèi)存,或者在main函數(shù)中手動(dòng)刪除內(nèi)存
class Singleton4
{
private:static Singleton4 singleton;Singleton4(){}
public:void func(){printf("調(diào)用func函數(shù)\n");}~Singleton4(){std::cout << "Singleton4析構(gòu)函數(shù)" << std::endl;}static Singleton4* getInstance(){return &singleton;}
};
Singleton4 Singleton4::singleton; // 靜態(tài)對(duì)象類外初始化
// 餓漢式的總結(jié)
// 由于在定義靜態(tài)變量的時(shí)候?qū)嵗瘑卫?#xff0c;因此在類加載的時(shí)候就已經(jīng)創(chuàng)建了單例對(duì)象,可確保單例對(duì)象的唯一性。線程是安全的。
// 缺點(diǎn):無論系統(tǒng)運(yùn)行時(shí)是否需要使用該單例對(duì)象,都會(huì)在類加載時(shí)創(chuàng)建對(duì)象,資源利用效率不高。// 懶漢式:需要時(shí)候再實(shí)例化單例對(duì)象。
// 懶漢式1:直接加個(gè)鎖。
// 這樣的代碼其實(shí)有個(gè)很嚴(yán)重的問題,就是代碼中可能需要頻繁調(diào)用getInstance這個(gè)函數(shù)
// 因?yàn)橹挥薪柚鷊etInstace這個(gè)函數(shù)才能獲取到單例類對(duì)象,然后才能調(diào)用單例類的其他成員
// 函數(shù)。為了解決一個(gè)初始化該類對(duì)象的互斥問題,居然在getInstace里面加了互斥量。導(dǎo)致
// 所有時(shí)刻,調(diào)用getInstance這個(gè)函數(shù),都會(huì)因?yàn)殒i互斥一下,嚴(yán)重影響性能。因?yàn)槌顺跏蓟瘯r(shí)刻,其他
// 時(shí)候完全不需要互斥。一旦初始化完成,if (singleton == nullptr)永遠(yuǎn)不會(huì)成立,所以singleton = new Singleton()
// 永遠(yuǎn)不會(huì)再次執(zhí)行。
class Singleton5
{
private:static Singleton5* singleton;static std::mutex my_mutex; //這里用的靜態(tài)成員變量,保證所有用到這個(gè)類的,用的是同一個(gè)互斥量。當(dāng)然定義一個(gè)全局互斥量也可以。Singleton5(){}class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; //這里會(huì)調(diào)用析構(gòu)函數(shù)singleton = nullptr;std::cout << "釋放內(nèi)存" << std::endl;}}};public:~Singleton5(){std::cout << "Singleton5析構(gòu)函數(shù)執(zhí)行" << std::endl;}static Singleton5* getInstance(){std::lock_guard<std::mutex> my_guard(my_mutex);if (singleton == nullptr){singleton = new Singleton5();static PtrCycle ptr;}return singleton;}void func(){printf("調(diào)用func函數(shù)\n");}};
std::mutex Singleton5::my_mutex;
Singleton5* Singleton5::singleton = nullptr;// 懶漢式2:雙重鎖定
// 雙重鎖定的寫法,保證線程1在if (singleton == nullptr)成立之后,
// singleton = new Singleton6();運(yùn)行之前,一定不會(huì)發(fā)生上下文的切換。
// 因此會(huì)創(chuàng)建完成單例類對(duì)象。然后互斥量解鎖之后,哪怕發(fā)生上下文切換,換到了另一個(gè)
// 線程,此時(shí)if (singleton == nullptr)一定不會(huì)成立,因此不會(huì)再調(diào)用第二次 singleton = new Singleton6()。
// 初始化時(shí)候,需要用到這個(gè)互斥量加鎖,其他時(shí)候并不會(huì)用到這個(gè)互斥量。因?yàn)橐坏┏跏蓟瓿芍?/span>
// if (singleton == nullptr)一定不會(huì)成立,因此不會(huì)因?yàn)檎{(diào)用一次getInstance就創(chuàng)建一次互斥量。
// 因此大大提升了代碼的運(yùn)行效率。class Singleton6
{
private:static Singleton6* singleton;static std::mutex my_mutex;Singleton6(){}class PtrCycle{public:~PtrCycle(){if (singleton){delete singleton; //這里會(huì)調(diào)用析構(gòu)函數(shù)singleton = nullptr;std::cout << "釋放內(nèi)存" << std::endl;}}};public:~Singleton6(){std::cout << "Singleton6析構(gòu)函數(shù)執(zhí)行" << std::endl;}static Singleton6* getInstance(){if (singleton == nullptr){std::lock_guard<std::mutex> my_guard(my_mutex);if (singleton == nullptr){singleton = new Singleton6();static PtrCycle ptr;}}return singleton;}void func(){printf("調(diào)用func函數(shù)\n");}};
std::mutex Singleton6::my_mutex;
Singleton6* Singleton6::singleton = nullptr;// C++11之后,靜態(tài)局部對(duì)象是實(shí)現(xiàn)多線程安全的單例類最佳寫法。
// C++11之后,多個(gè)線程同時(shí)初始化一個(gè)同一局部靜態(tài)對(duì)象,可以保證只初始化一次。
// 在實(shí)現(xiàn)單例的過程中要注意如下問題:
// 1. 構(gòu)造函數(shù)應(yīng)該聲明為非公有,從而禁止外界創(chuàng)建實(shí)例。
// 2. 拷貝操作和移動(dòng)操作也應(yīng)該禁止。
// 3. 只能通過 Singleton 的公有特定類操作訪問它的唯一實(shí)例(C++中的一個(gè)公有靜態(tài)成員函數(shù))
class Singleton7
{
public:~Singleton7(){std::cout << "Singleton7析構(gòu)函數(shù)執(zhí)行" << std::endl;}static Singleton7* getInstance(){static Singleton7 singleton_tmp;return &singleton_tmp;}void func(){printf("調(diào)用func函數(shù)\n");}private:Singleton7(){}// 拷貝構(gòu)造函數(shù)Singleton7(const Singleton7& singleton) = delete;// 拷貝賦值函數(shù)Singleton7& operator = (const Singleton7& singleton) = delete;// 移動(dòng)構(gòu)造函數(shù)Singleton7(Singleton7&& singleton) = delete;// 移動(dòng)賦值構(gòu)造函數(shù)Singleton7& operator = (Singleton7&& singleton) = delete;
};void my_thread()
{printf("thread run\n");Singleton3* s = Singleton3::getInstance();printf("address is: %p \n", s);s->func();
}int main()
{std::thread my_thread1(my_thread);std::thread my_thread2(my_thread);my_thread1.join();my_thread2.join();return 0;
}
不過有一點(diǎn)需要說明的是:將單例類放在主線程中,在其他子線程創(chuàng)建并運(yùn)行之前,將單例類初始化完成是強(qiáng)烈推薦的。這樣就不存在多個(gè)子線程對(duì)這個(gè)單例類對(duì)象訪問的沖突問題,因?yàn)橐坏┏跏蓟瓿?#xff0c;再次調(diào)用getInstance的操作全是讀操作,是線程安全的。
如果你需要在自己創(chuàng)建的子線程中創(chuàng)建單例類對(duì)象,為了保證多線程安全,可以參考我的代碼寫法。
總結(jié)
以上是生活随笔為你收集整理的C++线程安全单例类最全总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分辨率到底是个什么概念?它和DPI之间是
- 下一篇: C++ 线程安全的单例模式总结