【问题标题】:Singleton Synchronization C++单例同步 C++
【发布时间】:2012-04-05 08:05:14
【问题描述】:

如果我必须用 C++ 编写一个单例类,我将使用一个静态变量、私有构造函数和一个返回类对象的公共静态函数。但是在多线程环境中,代码会出现问题。为了避免多个线程同时访问同一个变量,Boost线程是用于同步的最佳机制吗?我的意思是设置/取消设置整个资源的锁/互斥锁。 C++ 标准库中是否还有其他内置的东西,我不必下载 boost、构建东西等?我听说过 C++ Ox,但不太了解。

【问题讨论】:

    标签: c++ design-patterns singleton ipc


    【解决方案1】:

    C++98/03 根本不支持线程。如果您使用的是 C++98 或 03 编译器,那么您几乎会被困在使用 Boost 或某些(或多或少)特定于操作系统的东西上,例如 pthread 或 Win32 的线程原语。

    C++11 有一个相当完整的线程支持库,有互斥体、锁、线程本地存储等。

    但是,我觉得有必要指出,备份并多考虑一下您是否需要/想要一个 Singleton 可能会更好。说得好听一点,单例模式在很大程度上已经失宠了。

    编辑:重读这篇文章,我有点跳过了我想说的一件事:至少当我使用它们时,任何/所有单例在任何辅助线程启动之前都已完全初始化。这使得在初始化时对线程安全的担忧完全没有意义。我想在启动辅助线程之前可能有一个你无法初始化的单例,所以你需要处理这个问题,但至少马上让我觉得这是一个相当不寻常的例外,我只会在/如果绝对必要的话。

    【讨论】:

    • 我没有看到任何迹象表明单例模式在实际从业者中已经失宠。 (当然,它也从未在他们中间被广泛使用。只有在适当的时候,这并不常见。)
    • 我见过很多Singletons,但没有一个是必要的。只需制作一个并传递它。
    • 粗略地说:Singleton 通常是设计错误。虽然...我同意记录方案很方便(比在每个函数中传递Logger 对象更方便)。
    • @JamesKanze:也许更准确的措辞是更多的从业者意识到他们是多么的不合适。
    • 我想我们大多数人从一开始就意识到了这一点。有几个特定的​​用途很方便,很少找到不使用一个或两个的应用程序。但同样罕见的是找到一个使用更多的。在我的应用程序中,我通常有三个:一个用于日志记录,一个用于配置数据,一个用于解析命令行参数。偶尔可能会有第四个,但仅此而已。自从我开始使用 C++ 以来,这种情况并没有改变。 (而且似乎对这种用途达成了共识——至少在我工作过的地方是这样。)
    【解决方案2】:

    对我来说,使用 c++11 实现单例的最佳方法是:

    class Singleton
    {
    public:
    static Singleton & Instance()
    {
        // Since it's a static variable, if the class has already been created,
        // It won't be created again.
        // And it **is** thread-safe in C++11.
    
        static Singleton myInstance;
    
        // Return a reference to our instance.
        return myInstance;
    }
    
    // delete copy and move constructors and assign operators
    Singleton(Singleton const&) = delete;             // Copy construct
    Singleton(Singleton&&) = delete;                  // Move construct
    Singleton& operator=(Singleton const&) = delete;  // Copy assign
    Singleton& operator=(Singleton &&) = delete;      // Move assign
    
    // Any other public methods
    
    protected:
    Singleton() 
    {
       // Constructor code goes here.
    }
    
    ~Singleton()
    {
        // Destructor code goes here.
    }
    
     // And any other protected methods.
    }
    

    这是一个 c++11 的特性,但是通过这种方式你可以创建一个线程安全的 Singleton。按照新标准,这个问题就不用管了。对象初始化将仅由一个线程进行,其他线程将等待它完成。或者你可以使用 std::call_once。

    如果你想独占访问单例的资源,你必须在这些函数中使用锁。

    不同类型的锁:

    使用 atomic_flg_lck:

    class SLock
    {
    public:
      void lock()
      {
        while (lck.test_and_set(std::memory_order_acquire));
      }
    
      void unlock()
      {
        lck.clear(std::memory_order_release);
      }
    
      SLock(){
        //lck = ATOMIC_FLAG_INIT;
        lck.clear();
      }
    private:
      std::atomic_flag lck;// = ATOMIC_FLAG_INIT;
    };
    

    使用原子:

    class SLock
    {
    public:
      void lock()
      {
        while (lck.exchange(true));
      }
    
      void unlock()
      {
        lck = true;
      }
    
      SLock(){
        //lck = ATOMIC_FLAG_INIT;
        lck = false;
      }
    private:
      std::atomic<bool> lck;
    };
    

    使用互斥锁:

    class SLock
    {
    public:
      void lock()
      {
        lck.lock();
      }
    
      void unlock()
      {
        lck.unlock();
      }
    
    private:
      std::mutex lck;
    };
    

    仅适用于 Windows:

    class SLock
    {
    public:
      void lock()
      {
        EnterCriticalSection(&g_crit_sec);
      }
    
      void unlock()
      {
        LeaveCriticalSection(&g_crit_sec);
      }
    
      SLock(){
        InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400);
      }
    
    private:
      CRITICAL_SECTION g_crit_sec;
    };
    

    atomic 和 and atomic_flg_lck 将线程保持在旋转计数中。 Mutex 只是让线程休眠。如果等待时间太长,最好让线程休眠。最后一个“CRITICAL_SECTION”使线程保持旋转计数,直到消耗了一段时间,然后线程进入睡眠状态。

    如何使用这些关键部分?

    unique_ptr<SLock> raiilock(new SLock());
    
    class Smartlock{
    public:
      Smartlock(){ raiilock->lock(); }
      ~Smartlock(){ raiilock->unlock(); }
    };
    

    使用 raii 成语。锁定临界区的构造函数和解锁临界区的析构函数。

    例子

    class Singleton {
    
       void syncronithedFunction(){
          Smartlock lock;
          //.....
       }
    
    }
    

    这个实现是线程安全和异常安全的,因为变量锁保存在堆栈中,所以当函数作用域结束(函数结束或异常)时,将调用析构函数。

    希望对您有所帮助。

    谢谢!!

    【讨论】:

      猜你喜欢
      • 2012-06-25
      • 1970-01-01
      • 1970-01-01
      • 2019-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-18
      相关资源
      最近更新 更多