假设你有一个多态Singleton类型(在我们的例子中是一个自定义的std::error_category
类型)。该类型是无状态的,因此没有数据成员,但它确实有几个虚函数。在多线程环境中实例化此类型时会出现问题。
实现这一目标的最简单方法是使用C ++ 11的magic statics:
my_type const& instantiate() {
static const my_type instance;
return instance;
}
不幸的是,我们的一个编译器(VC11)不支持此功能。
error_category
的实现中采取任何预防措施。他们只是在粗心大意,还是在这里担心比赛是不合理的偏执狂?error_category
,我希望尽可能避免进行堆分配。请记住,Singleton语义在这种情况下至关重要,因为错误类别的相等性由指针比较确定。这意味着例如线程本地存储不是一种选择。答案 0 :(得分:2)
以下是Casey的答案可能更简单的版本,该版本使用原子自旋锁来保护正常的静态声明。
my_type const& instantiate()
{
static std::atomic_int flag;
while (flag != 2)
{
int expected = 0;
if (flag.compare_exchange_weak(expected, 1))
break;
}
try
{
static my_type instance = whatever; // <--- normal static decl and init
flag = 2;
return instance;
}
catch (...)
{
flag = 0;
throw;
}
}
此代码也更容易转换为三个宏以供重用,在支持魔法静态的平台上很容易#defined
。
my_type const& instantiate()
{
MY_MAGIC_STATIC_PRE;
static my_type instance = whatever; // <--- normal static decl and init
MY_MAGIC_STATIC_POST;
return instance;
MY_MAGIC_STATIC_SCOPE_END;
}
答案 1 :(得分:1)
尝试#2b:使用atomic<int>
(std::once_flag
)实现您自己的等效Live at Rextester:
my_type const& instantiate() {
static std::aligned_storage<sizeof(my_type), __alignof(my_type)>::type storage;
static std::atomic_int flag;
while (flag < 2) {
// all threads spin until the object is properly initialized
int expected = 0;
if (flag.compare_exchange_weak(expected, 1)) {
// only one thread succeeds at the compare_exchange.
try {
::new (&storage) my_type;
} catch(...) {
// Initialization failed. Let another thread try.
flag = 0;
throw;
}
// Success!
if (!std::is_trivially_destructible<my_type>::value) {
std::atexit([] {
reinterpret_cast<my_type&>(storage).~my_type();
});
}
flag = 2;
}
}
return reinterpret_cast<my_type&>(storage);
}
这只依赖于编译器正确地初始化所有静态存储持续时间对象,并且还使用非标准扩展__alignof(<type>)
来正确对齐storage
,因为Microsoft的编译器团队不能被打扰添加没有两个下划线的关键字。
<小时/> 尝试#1:将
std::call_once
与std::once_flag
(Live demo at Coliru)结合使用:
my_type const& instantiate() {
struct empty {};
union storage_t {
empty e;
my_type instance;
constexpr storage_t() : e{} {}
~storage_t() {}
};
static std::once_flag flag;
static storage_t storage;
std::call_once(flag, []{
::new (&storage.instance) my_type;
std::atexit([]{
storage.instance.~my_type();
});
});
return storage.instance;
}
std::once_flag
的默认构造函数是constexpr
,所以它保证在常量初始化期间构造。我的印象是[需要引证] VC正确执行常量初始化。编辑:不幸的是,通过VS12的MSVC仍然不支持constexpr
,所以这种技术有一些未定义的行为。我会再试一次。
答案 2 :(得分:0)
该标准没有提到在多线程上调用函数时如何构造静态的问题。
gcc使用锁来使函数级静态线程安全(可以通过标志禁用)。大多数(所有?)版本的Visual C ++都没有线程安全的功能级静态。
建议在变量声明周围使用锁来保证线程安全。