这个单例实现是线程安全的吗?
Is this Singleton Implementation Thread-Safe?
我已经阅读了第 6 章中的问题和答案 here, and learned that a true thread-safe singleton can be implemented with correctly placed memory barrier, which is stated here。我已经提出了我自己的单例实现版本,通过查看代码,到目前为止我没有发现内存排序问题,但它与典型的 DCLP 方法有很大不同。您认为这是一个可靠的实现吗?
static std::atomic<T*> _instance;
...
Singleton* get_instance() {
ins = _instance.load(std::memory_order_acquire);
if (ins == nullptr) {
T* temp = nullptr;
{
std::lock_guard<std::mutex> guard(_lock);
temp = new(std::nothrow) T;
}
if(!_instance.compare_exchange_strong(nullptr, temp, std::memory_order_release)) {
delete temp;
}
}
return _instance.load(std::memory_order_acquire);
}
很难打败一个简单的
class Singleton { ... };
Singleton* get_instance()
{
static Singleton instance; // guaranteed to be thread safe in C++11
return &instance;
}
指针返回后对指针的任何访问仍然不是线程安全的,但在您的实现中也不是线程安全的。
Do you think this is a reliable implementation?
这取决于 class T
。一些 classes 必须是单例的,因为在任何给定时刻,物理上只能有一个实例。 IE。尝试创建第二个实例肯定会失败,例如,由于它们都需要独占访问某些独特的外部资源。
提议的实现 允许并发构造 T
的两个实例,这可能会导致极好的由竞争引起的错误。
编辑:
即使使用锁保护 new T
,此解决方案仍然不能保证 T
仅构造一次。
您的实施应仅用作教育工具(在实际软件中使用 static
)。
它有问题;可以创建多个对象(并且除一个之外的所有对象都会被再次删除),但至少它是线程安全的。
第一个 load
甚至可以是 mo_relaxed
,因为与 compare_exchange(release)
的同步包含在 load(mo_acquire)
return 语句中。
在 DCLP 实现中,您可以使用互斥体来避免创建多个对象并阻止正在等待创建者完成的线程。
由于您的实现已经允许多个对象,因此从正确性的角度来看 mutex
不是必需的(除非构造函数确实不是线程安全的)。
如果多个线程创建对象 T
,存储指向 _instance
的指针的线程释放对象(比如线程 N),
而其他人(比如 N+1)必须再次执行 load
; N+1 中的第二个 load(mo_acquire)
永远不会 return nullptr
。
线程 N+1 中的 compare_exchange
失败,因为它在修改顺序上比线程 N 中的第一次执行晚,
这意味着 compare_exchange
(在 N 中)在修改顺序中也先于第二个加载(在 N+1 中),因此保证 return 指向同步对象的对象指针
我已经阅读了第 6 章中的问题和答案 here, and learned that a true thread-safe singleton can be implemented with correctly placed memory barrier, which is stated here。我已经提出了我自己的单例实现版本,通过查看代码,到目前为止我没有发现内存排序问题,但它与典型的 DCLP 方法有很大不同。您认为这是一个可靠的实现吗?
static std::atomic<T*> _instance;
...
Singleton* get_instance() {
ins = _instance.load(std::memory_order_acquire);
if (ins == nullptr) {
T* temp = nullptr;
{
std::lock_guard<std::mutex> guard(_lock);
temp = new(std::nothrow) T;
}
if(!_instance.compare_exchange_strong(nullptr, temp, std::memory_order_release)) {
delete temp;
}
}
return _instance.load(std::memory_order_acquire);
}
很难打败一个简单的
class Singleton { ... };
Singleton* get_instance()
{
static Singleton instance; // guaranteed to be thread safe in C++11
return &instance;
}
指针返回后对指针的任何访问仍然不是线程安全的,但在您的实现中也不是线程安全的。
Do you think this is a reliable implementation?
这取决于 class T
。一些 classes 必须是单例的,因为在任何给定时刻,物理上只能有一个实例。 IE。尝试创建第二个实例肯定会失败,例如,由于它们都需要独占访问某些独特的外部资源。
提议的实现 允许并发构造 T
的两个实例,这可能会导致极好的由竞争引起的错误。
编辑:
即使使用锁保护 new T
,此解决方案仍然不能保证 T
仅构造一次。
您的实施应仅用作教育工具(在实际软件中使用 static
)。
它有问题;可以创建多个对象(并且除一个之外的所有对象都会被再次删除),但至少它是线程安全的。
第一个 load
甚至可以是 mo_relaxed
,因为与 compare_exchange(release)
的同步包含在 load(mo_acquire)
return 语句中。
在 DCLP 实现中,您可以使用互斥体来避免创建多个对象并阻止正在等待创建者完成的线程。
由于您的实现已经允许多个对象,因此从正确性的角度来看 mutex
不是必需的(除非构造函数确实不是线程安全的)。
如果多个线程创建对象 T
,存储指向 _instance
的指针的线程释放对象(比如线程 N),
而其他人(比如 N+1)必须再次执行 load
; N+1 中的第二个 load(mo_acquire)
永远不会 return nullptr
。
线程 N+1 中的 compare_exchange
失败,因为它在修改顺序上比线程 N 中的第一次执行晚,
这意味着 compare_exchange
(在 N 中)在修改顺序中也先于第二个加载(在 N+1 中),因此保证 return 指向同步对象的对象指针