如何防止多个线程在cpp中同时使用单例类实例

时间:2019-01-03 21:07:30

标签: c++ multithreading design-patterns singleton

我正在编写如下的线程安全单例类。以下实现可确保仅创建类的一个实例。我的用例是,我在多线程环境中使用该实例,在该环境中,每个线程都可以调用getInstance()并使用该实例做一些工作。我的问题是如何确保在特定时间只有一个线程在使用该实例,以防止在多个线程尝试同时使用单个实例时可能发生的竞争情况。

class Singleton {
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
 public:
    static Singleton& getInstance() {
        static Singleton s;
        return s;
    }
};

2 个答案:

答案 0 :(得分:5)

您可以做的一件事就是通过锁定互斥锁使其所有成员线程安全。

class Singleton {
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    std::mutex my_mutex_member;
 public:
    static Singleton& getInstance() {
        static Singleton s;
        return s;
    }
    void my_singleton_cool_function()
    {
        std::lock_guard<std::mutex> lg(my_mutex_member);
        // cool code here
    }
};

在上面的示例中,lg将锁定互斥锁,并且在函数的结尾,当lg被销毁时,析构函数将解锁互斥锁。这意味着只有一个线程可以同时运行该函数。这样,所有线程都可以引用单例,并且只有在两个或多个线程尝试同时执行相同操作时才会阻塞。

答案 1 :(得分:1)

  

我的问题是如何确保在特定时间只有一个线程在使用实例。

作为一般规则,您不能。

Nathan Oliver的答案适用于特殊情况,其中其他模块“使用实例”的唯一方法是调用其方法。在这种情况下,您可以确保这些方法中的每一个都锁定相同的互斥锁。

但是,如果您的单例暴露任何public数据成员,那么所有赌注都将消失:您将无法控制其他模块如何“使用”对象的公共数据成员。


当心!即使您将所有数据成员都设为私有,并使单例对象真正,真正地“线程安全”; still 不能保证其他代码的线程安全,这些代码可能取决于您的singleton对象与某些其他数据之间的某种关系。

线程安全并不是真正要确保“一次只有一个线程使用该对象”。线程安全与保留 invariant relationships 有关。例如,如果您有一个doubly-linked ring数据结构,则一个重要的不变式是p->next->prev必须始终等于p

想要在环中拼接新元素的线程必须暂时 break 不变量。在这种情况下,“线程安全”意味着确保没有其他线程能够看到临时中断的状态。

使用“线程安全”对象构建程序不会使整个程序成为“线程安全”对象,因为更高级别的程序可能依赖于对象之间的重要不变关系。而且,即使对象是单独个线程安全的对象,也无法让他们知道对于整个程序有意义的高级关系。