1. 程式人生 > 其它 >(C++11/14/17學習筆記):單例設計模式共享資料分析,call_once()函式

(C++11/14/17學習筆記):單例設計模式共享資料分析,call_once()函式

技術標籤:C++11/14/17

目錄

單例設計模式共享資料分析、解決,call_once

設計模式大概談

單例設計模式

單例設計模式共享資料問題分析、解決

std::call_once()

單例設計模式共享資料分析、解決,call_once

設計模式大概談

  • "設計模式":程式碼的一些寫法(這些寫法跟常規寫法還不怎麼一樣),程式靈活,維護起來可能方便,但是別人接管、閱讀程式碼都很痛苦。
  • 用“設計模式”理念寫出來的程式碼很晦澀的。
  • 老外應付特別大的專案的時候,把專案的開發經驗、模組劃分經驗,總結整理成設計模式(現有開發需求,後有理論總結和整理)。
  • 不要拿著一個程式(專案)往設計模式上套,一個小小的專案,把它非要弄幾個設計模式進去,本末倒置。
  • 設計模式肯定有它獨特的優點,要活學活用,不要深陷其中,生搬硬套。

單例設計模式

  • 單例:整個專案中,有某個或者某些特殊的類,屬於該類的物件,我只能建立1個,多了我建立不了。
  • 注意delete指標的技巧:類中套類,利用類物件回收時呼叫解構函式進行指標等資源的釋放。
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <list>
#include <mutex>

using namespace std;

class singleClass
{
private:
    singleClass() {};  //私有化建構函式
    static singleClass* m_instance;  //靜態成員變數

public:
    static singleClass* GetInstance()
    {
        if (m_instance == NULL)
        {
            m_instance = new singleClass();
            static GuiderPtr gc;
        }
        return m_instance;
    }
    class GuiderPtr   //類中套類,用來釋放物件
    {
    public:
        ~GuiderPtr()
        {
            if (singleClass::m_instance)
            {
                delete singleClass::m_instance;
                singleClass::m_instance = NULL;
            } 
        }  
    };

    void func() {
        cout << "This is a Test" << endl;
    }
};
    //類靜態變數初始化
singleClass*  singleClass::m_instance = NULL;

int main()
{
    singleClass* sg_ptr = singleClass::GetInstance();  //建立一個singleClass類,並返回指標
    singleClass* sg_ptr1 = singleClass::GetInstance();  //返回的之前建立的指標

    sg_ptr->func();
    sg_ptr1->func();
    singleClass::GetInstance()->func();

    return 0;
}

單例設計模式共享資料問題分析、解決

  • 建議:在建立所有其他執行緒之前,在主執行緒中創建出單例物件,載入資料,後續使用。
  • 實際可能面臨的問題:需要在我們自己建立的執行緒(而不是主執行緒)中來建立singleClass這個單例類的物件,這種執行緒可能不止一個(最少2個)。
  • 我們可能會面臨GetInstance()這種成員函式要互斥。
  • 雖然這兩個執行緒是同一個入口函式,但大家千萬要記住,這是兩個執行緒,所以這裡會有兩個流程(兩條通路)同時開始執行mythread這個函式。

考慮加鎖方式

#include <iostream>
#include <string>
#include <thread>
#include <mutex>

using namespace std;
std::mutex res_mutex;

class singleClass
{
private:
    singleClass() {};  //私有化建構函式
    static singleClass* m_instance;  //靜態成員變數

public:
    static singleClass* GetInstance()
    {
        //提過效率
        //a) 如果if (m_instance != nullptr)  條件成立,則肯定表示 m_instance 已經被new過了
        //b) 如果if (m_instance == nullptr) 不代表m_instance一定沒被new過
        if (m_instance == NULL) //雙重鎖定(雙重檢查)
        {
            std::unique_lock<std::mutex> mutex_getInstance(res_mutex); //自動加鎖
            if (m_instance == NULL)
            {
                m_instance = new singleClass();
                static GuiderPtr gc;
            }
        }
        return m_instance;
    }
    class GuiderPtr   //類中套類,用來釋放物件
    {
    public:
        ~GuiderPtr()
        {
            if (singleClass::m_instance)
            {
                delete singleClass::m_instance;
                singleClass::m_instance = NULL;
            } 
        }  
    };

    void func() {
        cout << "This is a Test" << endl;
    }
};
//類靜態變數初始化
singleClass*  singleClass::m_instance = NULL;

//執行緒入口函式
void mythread() {
    cout << "執行緒函式開始執行了" << endl;
    singleClass* sgPtr = singleClass::GetInstance();  //這裡可能有問題
    sgPtr->func();
    cout << "執行緒函式執行完畢了" << endl;
    return;
}


int main()
{
    std::thread t_obj1(mythread);
    std::thread t_obj2(mythread);
    
    t_obj2.join();
    t_obj1.join();
    
    return 0;
}

std::call_once()

  • C++11引入的函式,該函式的第二個引數是一個函式名 a()。
  • call_once 功能是能夠保證函式a()只被呼叫一次
  • call_once 具備互斥量這種能力,而且效率上,比互斥量消耗的資源更少。
  • call_once()需要與一個標記結合使用,這個標記 std::once_flag,其實once_flag是一個結構。
  • call_once()就是通過這個標記來決定對應的函式a()是否執行,呼叫call_once成功後,call_once()就把這個標記設定為一種已呼叫狀態。
    • 後續再次呼叫call_once() ,只要once_flag被設定為了“已呼叫”狀態,那麼對應的函式a()就不會再被執行了。
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <list>
#include <mutex>


using namespace std;
std::mutex res_mutex;
std::once_flag g_flag; //全域性變數,定義的標記

class singleClass
{
private:
    singleClass() {};  //私有化建構函式
    static singleClass* m_instance;  //靜態成員變數

    static void CreatInstance()      //只被呼叫一次
    {   
        std::chrono::milliseconds dura(2000);
        std::this_thread::sleep_for(dura);
        cout << "CreatInstance()執行了" << endl;

        m_instance = new singleClass();
        static GuiderPtr gc;
    }

public:
    static singleClass* GetInstance()
    {
        //兩個執行緒同時執行到這裡,其中一個執行緒要等另外一個執行緒執行完畢CreateInstance();
        std::call_once(g_flag, CreatInstance);
        cout << "GetInstance()執行了" << endl;
        return m_instance;
    }
    class GuiderPtr   //類中套類,用來釋放物件
    {
    public:
        ~GuiderPtr()
        {
            if (singleClass::m_instance)
            {
                delete singleClass::m_instance;
                singleClass::m_instance = NULL;
            } 
        }  
    };

    void func() {
        cout << "This is a Test" << endl;
    }
};
//類靜態變數初始化
singleClass*  singleClass::m_instance = NULL;

//執行緒入口函式
void mythread() {
    cout << "執行緒函式開始執行了" << endl;
    singleClass* sgPtr = singleClass::GetInstance();  //這裡可能有問題
    sgPtr->func();
    cout << "執行緒函式執行完畢了" << endl;
    return;
}


int main()
{
    std::thread t_obj1(mythread);
    std::thread t_obj2(mythread);
    
    t_obj2.join();
    t_obj1.join();
    
    return 0;
}

  • 綜上,還是建議優先在主執行緒中先建立單例物件。