我正在编写如下的线程安全单例类。以下实现可确保仅创建类的一个实例。我的用例是,我在多线程环境中使用该实例,在该环境中,每个线程都可以调用getInstance()
并使用该实例做一些工作。我的问题是如何确保在特定时间只有一个线程在使用该实例,以防止在多个线程尝试同时使用单个实例时可能发生的竞争情况。
class Singleton {
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
static Singleton s;
return s;
}
};
答案 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 不变量。在这种情况下,“线程安全”意味着确保没有其他线程能够看到临时中断的状态。
使用“线程安全”对象构建程序不会使整个程序成为“线程安全”对象,因为更高级别的程序可能依赖于对象之间的重要不变关系。而且,即使对象是单独个线程安全的对象,也无法让他们知道对于整个程序有意义的高级关系。