从我在cppref中发现一句话开始:
“注意:此功能的通常实现使用双重检查锁定模式的变体,这将已初始化的本地静态变量的运行时开销减少到单个非原子布尔比较。”
然后我尝试在不使用 std::call_once 的情况下自己实现它。然后我意识到 C++ 似乎并没有以完全标准的方式实现。这是我的实现的最终版本,但实际上依赖于“的概念”基于缓存同步的内存排序”。
#include <iostream>
#include <mutex>
#include <atomic>
template<typename T,auto = nullptr,size_t = 0>
class Static
{
struct alignas(std::hardware_destructive_interference_size) //To prevent the bool variable from being independently synchronized
Releaser
{
union
{
char _place_holder{};
T value;
};
bool constructed{};
~Releaser()
{
if (constructed)
{
value.~T();
}
}
};
inline static std::mutex s_m;
inline static Releaser s_value;
public:
static T &get()
{
if (s_value.constructed)
{
return s_value.value;
}
{
std::lock_guard l(s_m);
if (s_value.constructed)
{
return s_value.value;
}
new (&s_value.value) T();
std::atomic_thread_fence(std::memory_order_acq_rel);
s_value.constructed = true;
}
return s_value.value;
}
};
int main()
{
auto& a = Static<std::string,main>::get();
a = "1";
std::cout << Static<std::string,main>::get();
}
我什至怀疑大多数平台是否都是这样。
我想知道是否有任何纯标准的实现,而不需要丑陋的对齐方式和以前的假设。
您的实现不正确,因为
s_value.constructed
的初始测试是在没有持有锁的情况下执行的。这意味着一个线程可以读取 s_value.constructed
,而另一个线程(is 持有锁)同时执行 s_value.constructed = true
。因为 s_value.constructed
不是原子变量,所以同时读写会导致数据争用(这是未定义的行为)。
任何使用原子变量的算法都可以在没有原子变量的情况下实现。您只需将每个原子变量
x
替换为非原子变量 x
和互斥量 x_mutex
的组合,这样您就只能在 x
被保留时访问 x_mutex
(*)。但请注意,这将达不到双重检查锁定的目的,因为现在您“总是”必须获取互斥锁。您不妨删除第二个互斥锁,并在第一个互斥锁被保留时始终检查 constructed
标志。标准库提供了互斥体和原子变量的实现,因为用户无法在所有平台上以可移植的方式自行实现这些组件。您应该使用标准库互斥体和原子变量,而不是尝试重写它们。
(*) 在极少数需要顺序一致性的情况下,您需要一个互斥体来保护“所有”并发访问的变量,而不是每个变量一个。