初始化没有魔法静态的空多态Singleton类型

时间:2014-01-17 16:17:52

标签: multithreading c++11 singleton

假设你有一个多态Singleton类型(在我们的例子中是一个自定义的std::error_category类型)。该类型是无状态的,因此没有数据成员,但它确实有几个虚函数。在多线程环境中实例化此类型时会出现问题。

实现这一目标的最简单方法是使用C ++ 11的magic statics

my_type const& instantiate() {
    static const my_type instance;
    return instance;
}

不幸的是,我们的一个编译器(VC11)不支持此功能。

  • 我是否应该期望这会在多线程环境中爆炸?我很确定,就标准而言,所有的赌注都是关闭的。但鉴于该类型不包含任何数据成员且只包含虚函数,我应该从VC11等主流实现中得到什么样的错误?例如,Boost.System和VC似乎都没有在error_category的实现中采取任何预防措施。他们只是在粗心大意,还是在这里担心比赛是不合理的偏执狂?
  • 以符合标准的方式摆脱数据竞争的最佳方法是什么?由于这种情况下的类型是error_category,我希望尽可能避免进行堆分配。请记住,Singleton语义在这种情况下至关重要,因为错误类别的相等性由指针比较确定。这意味着例如线程本地存储不是一种选择。

3 个答案:

答案 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_oncestd::once_flagLive 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 ++都没有线程安全的功能级静态。

建议在变量声明周围使用锁来保证线程安全。