once_flag和call_once的簡(jiǎn)單實(shí)現(xiàn)

once_flag 和 call_once 用于保證某個(gè)函數(shù)能夠只執(zhí)行一次,能夠解決類似與單例模式中懶漢方式構(gòu)建對(duì)象中多線程不安全的問題。

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

using namespace std;

class OnceFlag{
private:
    mutex _mu;
    bool is_called{false};
public:
    operator bool(){
        return is_called;
    }
    OnceFlag& operator=(bool v){
        is_called = v;
        return *this;
    }
    mutex& getMutex(){
        return _mu;
    }
};

template <typename Func, typename... Ts>
void CallOnce(OnceFlag& flag, Func& f, Ts&&... args){
    if(!flag){
        lock_guard<mutex> lk(flag.getMutex());
        if(!flag){
            f(args...);
            flag = true;
        }
    }
}

#define MyFlag once_flag
#define MyCall call_once

// #define MyFlag OnceFlag
// #define MyCall CallOnce

MyFlag resource_flag;

void bar(int v){
    cout << "enter bar\n";
    this_thread::sleep_for(chrono::milliseconds(2000));
    cout << "exit bar\n";
}

void foo(){
    MyCall(resource_flag, bar, 1);
    cout << "exit foo\n"; 
}

int main(){
    thread t1(foo);
    thread t2(foo);
    thread t3(foo);
    t1.join(); t2.join(); t3.join();
}

下面回顧一下單例模式中懶漢方式構(gòu)建對(duì)象的過(guò)程:

  1. 無(wú)檢查模式:每次都加鎖,判斷實(shí)例是否構(gòu)建,缺點(diǎn)是影響效率;
class Singleton{
private:
    static Singleton* _ptr;
    static mutex mu;
    Singleton(){}
    Singleton(const Singleton&) = delete;
public:
    static Singleton* getInstance(){
        lock_guard<mutex> lk(mu); // 每次加鎖 性能差
        if(_ptr==nullptr){
            _ptr = new Singleton();
        }
        return _ptr;
    }
};
Singleton* Singleton::_ptr = nullptr;
mutex Singleton::mu;
  1. 單檢查(single-check)模式:先判斷是否已構(gòu)建,未構(gòu)建時(shí)再加鎖進(jìn)行構(gòu)建,缺點(diǎn)是仍然存在多次構(gòu)建的風(fēng)險(xiǎn);如果線程1執(zhí)行第9條語(yǔ)句后切換線程,線程2也剛好執(zhí)行完第九條語(yǔ)句,那么就會(huì)構(gòu)建兩次實(shí)例。
 class Singleton{
 private:
     static Singleton* _ptr;
     static mutex mu;
     Singleton(){}
     Singleton(const Singleton&) = delete;
 public:
     static Singleton* getInstance(){
         if(_ptr==nullptr){
             lock_guard<mutex> lk(mu);
             _ptr = new Singleton();
         }   
 
         return _ptr;
     }
 };
 Singleton* Singleton::_ptr = nullptr;
 mutex Singleton::mu;
  1. 雙檢查(double-check)模式:存在指令 reordered 問題;如下面代碼所示,當(dāng)線程1執(zhí)行第12條語(yǔ)句時(shí),這條語(yǔ)句并不是一條原子執(zhí)行的語(yǔ)句,它分為3個(gè)步驟:① 開辟內(nèi)存空間;②初始化內(nèi)存空間;③將指針賦給_ptr;其中第二步和第三步是會(huì)交換順序的,稱為指令的reoredered,如果第三步先執(zhí)行,并在這個(gè)時(shí)候發(fā)生了線程切換,另一個(gè)線程更好執(zhí)行第9條語(yǔ)句,發(fā)現(xiàn)_ptr已經(jīng)被賦值了,就會(huì)調(diào)用其成員函數(shù),不過(guò)因?yàn)檫@塊空間還沒有被初始化,就會(huì)造成執(zhí)行錯(cuò)誤;
class Singleton{
private:
    static Singleton* _ptr;
    static mutex mu;
    Singleton(){}
    Singleton(const Singleton&) = delete;
public:
    static Singleton* getInstance(){
        if(_ptr==nullptr){
            lock_guard<mutex> lk(mu);
            if(_ptr==nullptr){
                _ptr = new Singleton();
            }
        }
        
        return _ptr;
    }
};
Singleton* Singleton::_ptr = nullptr;
mutex Singleton::mu;
  1. 使用 call_once:
class Singleton{
private:
    static Singleton* _ptr;
    static once_flag _flag;
    Singleton(){}
    Singleton(const Singleton&) = delete;
public:
    static Singleton* getInstance(){
        call_once(_flag, [](){
            _ptr = new Singleton();
        });
        return _ptr;
    }
};
Singleton* Singleton::_ptr = nullptr;
once_flag _flag;
  1. 使用靜態(tài)變量:對(duì)于c++11以及后續(xù)的版本來(lái)說(shuō),構(gòu)建靜態(tài)變量是線程安全的。
class Singleton{
private:
    Singleton(){}
    Singleton(const Singleton&) = delete;
public:
    static Singleton* getInstance(){
        static Singleton instance;
        return &instance;
    }
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容