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