这里唯一未定义的行为是您从指针中读取,然后在不同的线程上写入它而没有同步。现在这对大多数指针来说可能没问题(特别是如果写入指针是原子的),但你可以很容易地明确:
std::atomic<SingletonType*> singleton;
std::mutex mtx;
SingletonType* get()
{
SingletonType* result = singleton.load(std::memory_order_relaxed);
if (!result) {
std::scoped_lock _(mtx);
result = singleton.load(std::memory_order_relaxed);
if (!result) {
result = new SingletonType();
singleton.store(result, std::memory_order_relaxed);
}
}
return result;
}
// Or with gcc builtins
SingletonType* singleton;
std::mutex mtx;
SingletonType* get()
{
SingletonType* result;
__atomic_load(&singleton, &result, __ATOMIC_RELAXED);
if (!result) {
std::scoped_lock _(mtx);
__atomic_load(&singleton, &result, __ATOMIC_RELAXED);
if (!result) {
result = new SingletonType();
__atomic_store(&singleton, &result, __ATOMIC_RELAXED);
}
}
return result;
}
不过,有一个更简单的实现方式:
SingletonType* get()
{
static SingletonType singleton;
return &singleton;
// Or if your class has a destructor
static SingletonType* singleton = new SingeltonType();
return singleton;
}
这通常也被实现为双重检查锁(除了隐藏的isSingletonConstructed bool 而不是指针是否为空)
您最初的担心似乎是new SingletonType() 等效于operator new(sizeof(SingletonType)),然后在获取的存储上调用构造函数,并且编译器可能会在分配指针后重新排序调用构造函数。但是,不允许编译器重新排序分配,因为这会产生明显的影响(就像您注意到另一个线程在构造函数仍在运行时返回 singleton)。
您的increment 函数可以同时读取和写入threshold(在第一次检查双重检查锁和获取互斥锁并递增threshold += 1000 之后),因此它可能存在竞争条件。
你可以这样修复它:
void increment()
{
int local = __atomic_fetch_add(&id, 1l, __ATOMIC_RELAXED);
if (local >= __atomic_load_n(&threshold, __ATOMIC_RELAXED)) {
const std::lock_guard<std::mutex> _(mtx);
int local_threshold = __atomic_load_n(&threshold, __ATOMIC_RELAXED);
if (local >= local_threshold) {
// Do periodic job every 1000 id interval
__atomic_store_n(&threshold, local_threshold + 1000, __ATOMIC_RELAXED);
}
}
}
但在这种情况下你并不真的需要原子,因为local 将是每个整数恰好一次(只要它只通过increment 修改),所以你可以改为:
// int id = 0;
// constexpr int threshold = 1000;
// std::mutex mtx; // Don't need if jobs can run in parallel
void increment()
{
int local = __atomic_fetch_add(&id, 1l, __ATOMIC_RELAXED);
if (local == 0) return;
if (local % threshold == 0) {
const std::lock_guard<std::mutex> _(mtx);
// Do periodic job every 1000 id interval
}
}