hello,各位看官好,本人最近在做項目的過程中遇到了多線程的單例問題,之後查詢資料才明白懶漢模式和餓漢模式是咋回事,于是就寫了一篇文章來總結這個東西。
一、關于單例模式的簡單說明
二、關于懶漢模式的說明
三、關于餓漢模式的說明
四、關于兩者的折中方法
五、總結
首先我們總結一下單例模式。單例模式其實嚴格來說也算得上是一種全局變量,它的核心原則就是利用靜态成員變量來實作全局隻有一個這樣的指針。它的重點有以下幾處:1、它的指針隻能是靜态的。2、在寫單例的時候需要把拷貝構造函數和運算符重載函數封掉,因為用不到。在書寫單例模式的時候有兩種寫法,第一種叫做懶漢模式,第二種叫做餓漢模式。兩者的不同之處在于前者是在用的時候執行個體化,後者是在一開始的時候執行個體化(可以把它看成标準的全局變量)
二、關于懶漢模式的總結
首先我們來說一下懶漢模式。懶漢模式的核心部分就在于在執行過程中來進行執行個體化。我們看一下代碼。
.h檔案
class singleton
{
public:
singleton();
virtual ~singleton() = default;
singleton(const singleton &rhs) = delete;
singleton operator=(const singleton &) = delete;
static std::shared_ptr<singleton> create();
void init();
void print();
private:
static std::shared_ptr<singleton> instance;
static int count;
};
.cpp檔案
std::shared_ptr<singleton> singleton::create()
if (instance == nullptr) {
instance = std::make_shared<singleton>();
if (instance != nullptr) {
instance->init();
}
}
return instance;
}
主函數:
singleton::create()
看到了嗎?他的核心原則就是在執行個體化的過程中來進行執行個體化。如果我們多調幾次,那麼隻會執行個體化一次。但是他不是線程安全的。我們可以寫一個小demo測試一下。
我們在主函數中這樣寫:
void threadfunc(void *p)
dword id = getcurrentthreadid(); // 獲得線程id
qdebug()<<"id is:"<<id;
singleton::create()->print(); // 構造函數并獲得執行個體,調用靜态成員函數
int main(int argc, char *argv[])
qapplication a(argc, argv);
mainwindow w;
handle m_thread[100];
for (int i=0;i<3;i++){
m_thread[i] = (handle)_beginthread(threadfunc,0,nullptr);
for(int i = 0; i<3; i++){
waitforsingleobject(m_thread[i], infinite);
w.show();
return a.exec();
在cpp中添加
singleton::singleton()
count++;
qdebug()<<"start";
sleep(1000);
qdebug()<<"end";
void hungrysinglton::print()
qdebug()<<"hcount is:"<<hcount;
簡單說一下代碼,目前我們建立一個子線程,在子線程中進行單例化,如果是線程安全的,那麼他會按照順序打出。比如說:
但是我們實際打出的值為:
以上程式說明我們三個線程都進行執行個體化了。是以這樣線程是不安全的。
之前也說了,餓漢模式是一開始就完成了初始化,他的過程可以這麼寫:
.h檔案的寫法
class hungrysinglton
hungrysinglton();
virtual ~hungrysinglton() = default;
hungrysinglton(const hungrysinglton &rhs) = delete;
hungrysinglton operator=(const hungrysinglton &rhs) = delete;
static std::shared_ptr<hungrysinglton> create();
static std::shared_ptr<hungrysinglton> instance;
static int hcount;
std::shared_ptr<hungrysinglton> hungrysinglton::instance = std::make_shared<hungrysinglton>(); //這是關鍵,在這裡就處理了
在進行中可以這樣寫:
std::shared_ptr<hungrysinglton> hungrysinglton::create()
這個好處就是能保證線程安全,但是會浪費記憶體。他的運作結果為:
我們看這樣隻運作了一次,線程是安全的。
四、關于兩者的折中辦法
還有一種方式就是加鎖,這樣既可以不浪費記憶體,又可以保證線程安全。
我們來看下面的代碼:
if (instance == nullptr) {
std::lock_guard<std::mutex> lk(m_mutex);
if (instance == nullptr) {
instance = std::make_shared<singleton>();
if (instance != nullptr) {
instance->init();
}
簡單解釋一下這個代碼:
首先第一個判斷是因為加鎖會很耗費記憶體,是以加一個判斷不用每一次判斷了。
加下來加鎖,就是保證後續的操作都是原子的。
這樣就能保證線程安全了。
簡單說一下,本文主要介紹了單例模式中懶漢模式和俄漢模式的差別以及寫法。需要說一下,在多cpu中,執行順序有可能改動,這樣也有可能會出問題,不過一般情況下這樣就可以了。