目前还不完全清楚为什么这不起作用.托管对象仍然构造两次:
/** Returns an object with static storage duration.
This is a workaround for Visual Studio 2013 and earlier non-thread
safe initialization of function local objects with static storage duration.
Usage:
@code
my_class& foo()
{
static static_initializer <my_class> instance;
return *instance;
}
@endcode
*/
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)
{
#ifdef _MSC_VER
static std::aligned_storage <sizeof(T),
std::alignment_of <T>::value>::type storage;
instance_ = reinterpret_cast<T*>(&storage);
// Double checked lock:
// 0 = unconstructed
// 1 = constructing
// 2 = constructed
//
static long volatile state; // zero-initialized
if (state != 2)
{
struct destroyer
{
T* t_;
destroyer (T* t) : t_(t) { }
~destroyer() { t_->~T(); }
};
for(;;)
{
long prev;
prev = InterlockedCompareExchange(&state, 1, 0);
if (prev == 0)
{
try
{
::new(instance_) T(std::forward<Args>(args)...);
static destroyer on_exit (instance_);
InterlockedIncrement(&state);
}
catch(...)
{
InterlockedDecrement(&state);
throw;
}
}
else if (prev == 1)
{
std::this_thread::sleep_for (std::chrono::milliseconds(10));
}
else
{
assert(prev == 2);
break;
}
}
}
#else
static T object(std::forward<Args>(args)...);
instance_ = &object;
#endif
}
最佳答案 我相信以下代码是正确的.它通过了单元测试.原始代码的问题是Visual Studio 2013及更早版本使用简单的bool保护函数本地对象的构造函数.在调用构造函数之前,bool设置为true.因此,其他线程可以将对象看作是完全构造的,而不是.问题中发布的实现是不正确的,因为get()函数可以在完全构造之前访问托管对象.
这个新实现通过旋转来保护get(),直到对象完全构造.剩下的大部分变化都围绕着将状态数据提供给其他成员函数.
任何支持C 11并且是2013或更早版本的Visual Studio的任何人都遇到函数本地静态不是线程安全的问题可以替换:
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 {
/** Returns an object with static storage duration.
This is a workaround for Visual Studio 2013 and earlier non-thread
safe initialization of function local objects with static storage duration.
Usage:
@code
my_class& foo()
{
static static_initializer <my_class> instance;
return *instance;
}
@endcode
*/
#ifdef _MSC_VER
template <
class T,
class Tag = void
>
class static_initializer
{
private:
struct data_t
{
// 0 = unconstructed
// 1 = constructing
// 2 = constructed
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 _; // zero-initialized
return _;
}
template <class T, class Tag>
template <class... Args>
static_initializer <T, Tag>::static_initializer (Args&&... args)
{
data_t& _(data());
// Double Checked Locking Pattern
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(...)
{
// Constructors that throw exceptions are unsupported
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:
// Used to create separate instances for each test
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)
{
}
// number of calls to the constructor
std::atomic <long> calls;
// incremented after construction completes
std::atomic <long> constructed;
// increment when class is accessed before construction
std::atomic <long> access;
};
/* This testing singleton detects two conditions:
1. Being accessed before getting fully constructed
2. Getting constructed twice
*/
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
// Visual Studio 2013 and earlier can exhibit both double
// construction, and access before construction when function
// local statics are initialized concurrently.
//
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);
}