【发布时间】:2013-08-18 22:07:30
【问题描述】:
Visual Studio 2012 没有实现线程安全静态初始化的 C++11 标准 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm)。我有一个本地静态函数,我需要保证它会以线程安全的方式进行初始化。以下是 Visual Studio 2012 中的不是线程安全的:
struct MyClass
{
int a;
MyClass()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
a = 5;
}
};
void foo()
{
static MyClass instance;
std::cout << instance.a << '\n';
}
int main()
{
std::thread a(foo);
std::thread b(foo);
a.join();
b.join();
system("pause");
}
上述程序在 Visual Studio 2012 上的输出很可能是:
0
5
我需要解决这个问题,我正在尝试找到一种方法来仅使用函数局部静态(没有全局或类级静态)。
我最初的想法是使用互斥锁,但它遇到了同样的静态初始化线程安全问题。如果我在 foo 中有一个静态 st::mutex ,那么第二个线程可能会在它处于无效状态时获得互斥锁的副本。
另一个选项是添加一个 std::atomic_flag 自旋锁。问题是,std::atomic_flag 初始化线程在 Visual Studio 2012 中是否安全?
void foo()
{
// is this line thread safe?
static std::atomic_flag lock = ATOMIC_FLAG_INIT;
// spin lock before static construction
while (lock.test_and_set(std::memory_order_acquire));
// construct an instance of MyClass only once
static MyClass instance;
// end spin lock
lock.clear(std::memory_order_release);
// the following is not thread safe
std::cout << instance.a << '\n';
}
在上面的代码中,两个线程是否有可能通过自旋锁,还是保证只有其中一个可以通过?不幸的是,我想不出一种简单的方法来测试它,因为我不能在 atomic_flag 初始化程序中放入一些东西来减慢它,就像我在一个类中可以做到的那样。但是,我想确保我的程序不会因为我做了一个无效的假设而突然崩溃。
【问题讨论】:
-
我对同样的问题感到困惑。由于“函数局部静态”是static init order fiasco 的经典答案,VS 让我们与此紧密结合!
-
自旋锁守卫是我最终解决问题的方法。一定要包括内存顺序的东西,否则由于编译器/CPU的内存重新排序,你仍然可能有竞争条件!一旦初始化第一次完成,上面的代码应该几乎永远不会旋转,因为它在很少的周期内获取并清除锁。如果这是一段对性能至关重要的代码,您可能可以使用一个非易失性布尔值来更好地包装整个事情,它只会从假到真(从不从真到假)以避免潜在的核心同步。
标签: c++ visual-studio-2012 c++11