使用原子的线程安全单例
Posted
技术标签:
【中文标题】使用原子的线程安全单例【英文标题】:Thread-safe singleton using atomic 【发布时间】:2020-07-22 07:35:28 【问题描述】:只想知道atomic_flag = 1;
是否保持 myclass_st 分配线程安全。 (不好意思我的例子有这么多问题,所以改了。)
myClass* myclass_st = nullptr;
std::atomic<int> atomic_flag;
std::mutex mtx; //err
myClass* get_instance()
//std::unique_lock<std::mutex> lk(mtx);
if (myclass_st == nullptr)
mtx.lock();
if (myclass_st == nullptr)
myclass_st = new myClass();
atomic_flag = 1;
mtx.unlock(); //err
return myclass_st;
我知道我们可以在 c11 之后使用static
。
也许我应该像这样修改代码?
myClass* myclass_st = nullptr;
std::atomic<int> atomic_flag;
myClass* get_instance()
if (atomic_flag.load() == 0)
std::unique_guard<std::mutex> lk(mtx);
if (atomic_flag.load() == 0)
myclass_st = new myClass();
atomic_flag = 1;
return myclass_st;
【问题讨论】:
在 unique_lock 之后,互斥锁已经被锁定。您的显式锁定和解锁不一致。unique_lock
更好,因为一旦抛出异常或超出范围,unique_lock
将展开并解锁线程。但是std::mutex
不是。
互斥锁对线程安全没有任何作用,每次调用get_instance
都会生成一个新的mtx
,所以它不会同步任何东西。另外,如果从未检查过,为什么还要使用原子标志?
您正在检查非原子myclass_st
,而另一个线程(已锁定互斥锁)可以修改它。你需要检查atomic_flag
。
您为什么认为atomic_flag = 1
会使myclass_st
分配线程安全?
【参考方案1】:
显示的代码不是线程保存的,因为std::mutex mtx;
每次都是一个新对象。 std::mutex mtx;
必须是 static so that it is the same mutex for each invocation of
get_instance`,但即便如此,手动锁定和解锁互斥锁在当前表单中也无效。
EDIT 将std::mutex mtx;
移出函数后,对于get_instance
的每次调用,互斥锁都是相同的。但它仍然不是线程保存。多个线程可以通过第一个if (myclass_st == nullptr)
条件,其中myclass_st
是nullptr
。如果有例如超过三个线程通过第一个if
然后第一个调用lock
的线程将设置myclass_st
并释放它对互斥锁的锁定。调用lock
的第二个线程不会释放其获得的锁,因此通过第一个if
的所有其他线程都将被阻塞。
必须是:
myClass* get_instance()
mtx.lock();
if (myclass_st == nullptr)
myclass_st = new myClass();
atomic_flag = 1;
mtx.unlock();
return myclass_st;
您通常希望使用具有自动存储持续时间(RAII 习惯用法)的锁守卫来进行锁定,而不是手动锁定和解锁,因为这样可以确保在离开 get_instance
时始终释放互斥锁上的锁。
myClass* get_instance()
std::lock_guard<std::mutex> lk(mtx);
if (myclass_st == nullptr)
myclass_st = new myClass();
atomic_flag = 1;
return myclass_st;
编辑
不,第一个和第二个示例都不是线程保存。对于您展示的第二个示例,if (atomic_flag.load() == 0) /** ... **/ atomic_flag = 1;
仍然可以由两个线程输入。所以new myClass
仍然可以重复多次。
【讨论】:
第二个例子是最佳实践 (RAII)。如果new
失败或构造函数失败,第一个示例将无法解锁mtx
。
谢谢。这是安全的。但是每个线程都必须获得互斥锁。成本很高。
@Anna 这不是你最初的问题。正如我在评论中已经提到的那样,您不断改变您的问题及其范围。最初,您询问您在初始问题中显示的代码和第一次编辑是否是线程保存。
Err.. 我只是想专注于如何使 new
原子化。有些问题与我想知道的无关。所以我改变了。我会换个问题。谢谢。【参考方案2】:
只想知道 atomic_flag = 1;保持 myclass_st 分配线程安全与否。
没有。
也许我应该像这样修改代码?
myClass* myclass_st = nullptr; std::atomic<int> atomic_flag; myClass* get_instance() if (atomic_flag.load() == 0) myclass_st = new myClass(); atomic_flag = 1; return myclass_st;
如果你打算 get_instance
被多个线程调用,那么不会。
我猜你想要:
myClass* myclass_st = nullptr;
std::atomic<int> atomic_flag0;
std::mutex mtx;
myClass* get_instance()
if (atomic_flag == 0)
std::unique_lock<std::mutex> lk(mtx);
if (myclass_st == nullptr)
myclass_st = new myClass();
atomic_flag = 1;
return myclass_st;
【讨论】:
以上是关于使用原子的线程安全单例的主要内容,如果未能解决你的问题,请参考以下文章