我相信以下代码是正确的,因为它通过了单元测试。原始代码的问题在于,Visual Studio 2013及更早版本使用一个简单的bool来保护函数内局部对象的构造函数。当调用构造函数时,bool被设置为true。 因此,其他线程可以看到未完全构造的对象。问题中发布的实现是不正确的,因为get()函数可以在对象完全构造之前访问托管对象。
这个新实现通过旋转等待对象完全构造来保护get()。其余的大部分更改围绕着使状态数据可用于其他成员函数。
任何使用支持C++11的Visual Studio版本(包括2013或更早版本)且遇到函数内静态变量不安全的问题的人都可以将以下内容替换:
void example()
{
static MyObject foo;
foo.bar();
}
使用
void example()
{
beast::static_initializer <MyObject> foo;
foo->bar();
}
解决函数本地具有静态存储期的对象同时访问的问题。代码如下:
#ifndef BEAST_UTILITY_STATIC_INITIALIZER_H_INCLUDED
#define BEAST_UTILITY_STATIC_INITIALIZER_H_INCLUDED
#include <beast/utility/noexcept.h>
#include <utility>
#ifdef _MSC_VER
#include <cassert>
#include <chrono>
#include <new>
#include <type_traits>
#include <intrin.h>
#endif
namespace beast {
#ifdef _MSC_VER
template <
class T,
class Tag = void
>
class static_initializer
{
private:
struct data_t
{
long volatile state;
typename std::aligned_storage <sizeof(T),
std::alignment_of <T>::value>::type storage;
};
struct destroyer
{
T* t_;
explicit destroyer (T* t) : t_(t) { }
~destroyer() { t_->~T(); }
};
static
data_t&
data() noexcept;
public:
template <class... Args>
explicit static_initializer (Args&&... args);
T&
get() noexcept;
T&
operator*() noexcept
{
return get();
}
T*
operator->() noexcept
{
return &get();
}
};
template <class T, class Tag>
auto
static_initializer <T, Tag>::data() noexcept ->
data_t&
{
static data_t _;
return _;
}
template <class T, class Tag>
template <class... Args>
static_initializer <T, Tag>::static_initializer (Args&&... args)
{
data_t& _(data());
if (_.state != 2)
{
T* const t (reinterpret_cast<T*>(&_.storage));
for(;;)
{
long prev;
prev = InterlockedCompareExchange(&_.state, 1, 0);
if (prev == 0)
{
try
{
::new(t) T (std::forward<Args>(args)...);
static destroyer on_exit (t);
InterlockedIncrement(&_.state);
}
catch(...)
{
std::terminate();
}
}
else if (prev == 1)
{
std::this_thread::yield();
}
else
{
assert(prev == 2);
break;
}
}
}
}
template <class T, class Tag>
T&
static_initializer <T, Tag>::get() noexcept
{
data_t& _(data());
for(;;)
{
if (_.state == 2)
break;
std::this_thread::yield();
}
return *reinterpret_cast<T*>(&_.storage);
}
#else
template <
class T,
class Tag = void
>
class static_initializer
{
private:
T* instance_;
public:
template <class... Args>
explicit
static_initializer (Args&&... args);
T&
get() noexcept
{
return *instance_;
}
T&
operator*() noexcept
{
return get();
}
T*
operator->() noexcept
{
return &get();
}
};
template <class T, class Tag>
template <class... Args>
static_initializer <T, Tag>::static_initializer (Args&&... args)
{
static T t (std::forward<Args>(args)...);
instance_ = &t;
}
#endif
}
#endif
#include <beast/utility/static_initializer.h>
#include <beast/unit_test/suite.h>
#include <atomic>
#include <condition_variable>
#include <sstream>
#include <thread>
#include <utility>
namespace beast {
static_assert(__alignof(long) >= 4, "");
class static_initializer_test : public unit_test::suite
{
public:
struct cxx11_tag { };
struct beast_tag { };
template <std::size_t N, class Tag>
struct Case
{
enum { count = N };
typedef Tag type;
};
struct Counts
{
Counts()
: calls (0)
, constructed (0)
, access (0)
{
}
std::atomic <long> calls;
std::atomic <long> constructed;
std::atomic <long> access;
};
template <class Tag>
class Test;
template <class Function>
static
void
run_many (std::size_t n, Function f);
template <class Tag>
void
test (cxx11_tag);
template <class Tag>
void
test (beast_tag);
template <class Tag>
void
test();
void
run ();
};
template <class Tag>
class static_initializer_test::Test
{
public:
explicit
Test (Counts& counts);
void
operator() (Counts& counts);
};
template <class Tag>
static_initializer_test::Test<Tag>::Test (Counts& counts)
{
++counts.calls;
std::this_thread::sleep_for (std::chrono::milliseconds (10));
++counts.constructed;
}
template <class Tag>
void
static_initializer_test::Test<Tag>::operator() (Counts& counts)
{
if (! counts.constructed)
++counts.access;
}
template <class Function>
void
static_initializer_test::run_many (std::size_t n, Function f)
{
std::mutex mutex;
std::condition_variable cond;
std::atomic <bool> start (false);
std::vector <std::thread> threads;
threads.reserve (n);
{
std::unique_lock <std::mutex> lock (mutex);
for (auto i (n); i-- ;)
{
threads.emplace_back([&]()
{
{
std::unique_lock <std::mutex> lock (mutex);
while (! start.load())
cond.wait(lock);
}
f();
});
}
start.store (true);
}
cond.notify_all();
for (auto& thread : threads)
thread.join();
}
template <class Tag>
void
static_initializer_test::test (cxx11_tag)
{
testcase << "cxx11 " << Tag::count << " threads";
Counts counts;
run_many (Tag::count, [&]()
{
static Test <Tag> t (counts);
t(counts);
});
#ifdef _MSC_VER
expect (counts.constructed > 1 || counts.access > 0);
#else
expect (counts.constructed == 1 && counts.access == 0);
#endif
}
template <class Tag>
void
static_initializer_test::test (beast_tag)
{
testcase << "beast " << Tag::count << " threads";
Counts counts;
run_many (Tag::count, [&counts]()
{
static static_initializer <Test <Tag>> t (counts);
(*t)(counts);
});
expect (counts.constructed == 1 && counts.access == 0);
}
template <class Tag>
void
static_initializer_test::test()
{
test <Tag> (typename Tag::type {});
}
void
static_initializer_test::run ()
{
test <Case< 4, cxx11_tag>> ();
test <Case< 16, cxx11_tag>> ();
test <Case< 64, cxx11_tag>> ();
test <Case<256, cxx11_tag>> ();
test <Case< 4, beast_tag>> ();
test <Case< 16, beast_tag>> ();
test <Case< 64, beast_tag>> ();
test <Case<256, beast_tag>> ();
}
BEAST_DEFINE_TESTSUITE(static_initializer,utility,beast);
}
static_initializer
本身的一部分?另外,既然它只在Windows上有效,为什么不使用yield()
而不是sleep? - Mooing Duckstatic long volatile state
只被初始化一次为零,您是否绝对100%确定volatile
能够避开您试图解决的错误? - Mooing Duck