美文网首页
once_flag和call_once的简单实现

once_flag和call_once的简单实现

作者: FoolishFlyFox | 来源:发表于2019-10-10 15:53 被阅读0次

once_flag 和 call_once 用于保证某个函数能够只执行一次,能够解决类似与单例模式中懒汉方式构建对象中多线程不安全的问题。

#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();
}

下面回顾一下单例模式中懒汉方式构建对象的过程:

  1. 无检查模式:每次都加锁,判断实例是否构建,缺点是影响效率;
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)模式:先判断是否已构建,未构建时再加锁进行构建,缺点是仍然存在多次构建的风险;如果线程1执行第9条语句后切换线程,线程2也刚好执行完第九条语句,那么就会构建两次实例。
 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 问题;如下面代码所示,当线程1执行第12条语句时,这条语句并不是一条原子执行的语句,它分为3个步骤:① 开辟内存空间;②初始化内存空间;③将指针赋给_ptr;其中第二步和第三步是会交换顺序的,称为指令的reoredered,如果第三步先执行,并在这个时候发生了线程切换,另一个线程更好执行第9条语句,发现_ptr已经被赋值了,就会调用其成员函数,不过因为这块空间还没有被初始化,就会造成执行错误;
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. 使用静态变量:对于c++11以及后续的版本来说,构建静态变量是线程安全的。
class Singleton{
private:
    Singleton(){}
    Singleton(const Singleton&) = delete;
public:
    static Singleton* getInstance(){
        static Singleton instance;
        return &instance;
    }
};

相关文章

网友评论

      本文标题:once_flag和call_once的简单实现

      本文链接:https://www.haomeiwen.com/subject/vaqapctx.html