日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++线程安全单例类最全总结

發布時間:2023/12/10 c/c++ 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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++线程安全单例类最全总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。