我遇到了以下的Singleton模式
在上述代码中,栅栏的好处是什么?
编辑
据我所见,这两种情况的主要区别在于,在经典示例中,我们使用内存屏障,因为我们处理了2个变量 - 因此我们面临Thread 2在存储x之前存储f或在Thread 1加载f之前加载x的“危险”。
但在我的Singleton代码中,内存屏障旨在防止可能的内存重排序是什么?
注意
我知道有其他方法(也许更好)来实现这一点,我的问题是出于教育目的 - 我正在学习关于内存屏障,并好奇地知道在这种特定情况下它们是否有用。因此,请忽略与此问题无关的所有其他事项。
get_instance
函数实现:template<typename T>
T* Singleton<T>::get_instance()
{
static std::unique_ptr<T> destroyer;
T* temp = s_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (temp == nullptr)
{
std::lock_guard<std::mutex> lock(s_mutex);
temp = s_instance.load(std::memory_order_relaxed);/* read current status of s_instance */
if (temp == nullptr)
{
temp = new T;
destroyer.reset(temp);
std::atomic_thread_fence(std::memory_order_release);
s_instance.store(temp, std::memory_order_relaxed);
}
}
return temp;
}
我在想 - 在获取和释放内存屏障中是否有任何价值?据我所知 - 内存屏障旨在防止在两个不同变量之间重新排列内存操作。让我们拿经典的例子来说:
(这都是伪代码 - 不要被语法卡住)
# Thread 1
while(f == 0);
print(x)
# Thread 2
x = 42;
f = 1;
在这种情况下,我们希望防止线程2中2个存储操作的重排序以及线程1中2个加载操作的重排序。因此,我们插入屏障:
# Thread 1
while(f == 0)
acquire_fence
print(x)
# Thread 2
x = 42;
release_fence
f = 1;
在上述代码中,栅栏的好处是什么?
编辑
据我所见,这两种情况的主要区别在于,在经典示例中,我们使用内存屏障,因为我们处理了2个变量 - 因此我们面临Thread 2在存储x之前存储f或在Thread 1加载f之前加载x的“危险”。
但在我的Singleton代码中,内存屏障旨在防止可能的内存重排序是什么?
注意
我知道有其他方法(也许更好)来实现这一点,我的问题是出于教育目的 - 我正在学习关于内存屏障,并好奇地知道在这种特定情况下它们是否有用。因此,请忽略与此问题无关的所有其他事项。
static T inst; return &inst;
?这样可以完全线程安全,不需要动态分配内存,并且在性能方面很难被超越。 - Bentemp
是一个指针,指向的内存由构造函数写入。将指针释放存储到s_instance
上确保加载指针并解引用它的事物将看到有效数据。在某些机器上,s_instance.store(temp, mo_release)
更有效,并且更容易输入,因此您实际上不需要分离栅栏,除非您坚持使用mo_relaxed
。(至少我没有看到使用单独栅栏的理由。) - Peter Cordestemp
分配给s_instance
的赋值操作发生在temp
被分配之后? - YoavKleintemp = new T
和s_instance.store(temp, mo_relaxed)
的顺序不能被重新排序,因为这些操作之间存在明显的因果关系。如果发生了这种情况 - 那么NULL
将被存储在s_instance
中,并且它不会改变!与最终两个都将对其他线程可见的x
和f
不同... - YoavKlein