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ò)程:
- 無(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;
- 單檢查(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;
- 雙檢查(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;
- 使用 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;
- 使用靜態(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;
}
};