我正在基于“Head First Design Patterns”书中的示例在 C++ 中实现线程安全的单例模式,该书最初在 Java 中介绍了该模式。我知道从 C++11 开始,静态局部变量的初始化是线程安全的。
但是,我不确定互斥锁相对于静态类方法中的 getInstance()
调用的位置。
mutex
应该在
getInstance()
调用之前还是之后锁定?这是相关的代码片段: 我认为应该在之后,因为
ChocolateBoilerSingleton::getInstance();
是线程安全的。但我想在这里了解其他意见。谢谢你。
#include <mutex>
class ChocolateBoilerSingleton
{
public:
ChocolateBoilerSingleton(const ChocolateBoilerSingleton&) = delete;
ChocolateBoilerSingleton& operator=(const ChocolateBoilerSingleton&) = delete;
static void fill()
{
auto& instance = ChocolateBoilerSingleton::getInstance();
std::lock_guard<std::mutex> lock(instance.m_mtx);
if (instance.isEmpty())
{
instance.m_empty = false;
instance.m_boiled = false;
}
}
static void boil()
{
auto& instance = ChocolateBoilerSingleton::getInstance();
std::lock_guard<std::mutex> lock(instance.m_mtx);
if (!instance.isEmpty() && !instance.isBoiled())
{
instance.m_boiled = true;
}
}
static void drain()
{
auto& instance = ChocolateBoilerSingleton::getInstance();
std::lock_guard<std::mutex> lock(instance.m_mtx);
if (!instance.isEmpty() && instance.isBoiled())
{
instance.m_empty = true;
}
}
public:
static ChocolateBoilerSingleton& getInstance()
{
static ChocolateBoilerSingleton instance;
return instance;
}
bool isBoiled() const
{
return this->m_boiled;
}
bool isEmpty() const
{
return this->m_empty;
}
// Data --------------------
private:
ChocolateBoilerSingleton() :
m_mtx(),
m_empty(true),
m_boiled(false)
{}
std::mutex m_mtx;
bool m_empty;
bool m_boiled;
};
int main()
{
ChocolateBoilerSingleton::fill();
ChocolateBoilerSingleton::boil();
ChocolateBoilerSingleton::drain();
}
fill()
、
boil()
和
drain()
不必是静态的。调用者应该使用
getInstance()
方法来获取对单例对象的引用,并使用该引用调用这些成员函数。
void fill() {
std::lock_guard<std::mutex> lock(m_mtx);
if (isEmpty()) {
m_empty = false;
m_boiled = false;
}
}
同样的事情适用于 boil()
和
drain()
,你的
main()
函数将是这样的。
int main() {
auto& boiler = ChocolateBoilerSingleton::getInstance();
boiler.fill();
boiler.boil();
boiler.drain();
return 0;
}
还有一件事值得一提。
锁定你的const getter方法。这可能会很奇怪,因为你以只读方式访问m_boiled
和
m_empty
,但是任何可以由另一个线程写入的数据都应该被锁定即使你正在阅读它。在您的情况下,
fill()
和
boil()
可以在您尝试读取时更新变量。
bool isBoiled() const {
std::lock_guard<std::mutex> lock(m_mtx);
return this->m_boiled;
}
不要忘记将 m_mtx
标记为
mutable
,因为 const 成员无法锁定互斥体。
mutable std::mutex m_mtx;
另外,你可以考虑使用std::atomic<bool>
,可以去掉
mutex
。