线程安全的静态变量没有静音?

时间:2009-06-27 05:30:49

标签: c++ boost initialization thread-safety c++03

我记得读过在方法中声明的静态变量不是线程安全的。 (请参阅What about the Meyer's singleton?提到的Todd Gardner

Dog* MyClass::BadMethod()
{
  static Dog dog("Lassie");
  return &dog;
}

我的库为最终用户生成C ++代码,以便作为其应用程序的一部分进行编译。它生成的代码需要以线程安全的跨平台方式初始化静态变量。我想使用boost::call_once来互换变量初始化,但最终用户会接触到Boost依赖项。

有没有办法让我在不强迫最终用户额外依赖的情况下这样做?

5 个答案:

答案 0 :(得分:10)

你是正确的,这样的静态初始化不是线程安全的(here是一篇讨论编译器将其转化为什么的文章)

目前,没有标准的,线程安全的,可移植的方式来初始化静态单例。可以使用双重检查锁定,但是您需要潜在的非便携式线程库(请参阅讨论here)。

如果线程安全是必须的话,这里有几个选项:

  1. 不要懒惰(加载):在静态初始化期间初始化。如果另一个静态在其构造函数中调用此函数,则可能会出现问题,因为静态初始化的顺序未定义(请参阅here)。
  2. 使用boost(如你所说)或Loki
  3. 滚动你的 在您支持的平台上拥有自己的单例 (应该避免,除非 你是一个线程专家)
  4. 每次需要访问时锁定互斥锁。这可能会很慢。
  5. 1的示例:

    // in a cpp:
    namespace {
        Dog dog("Lassie");
    }
    
    Dog* MyClass::BadMethod()
    {
      return &dog;
    }
    

    4的例子:

    Dog* MyClass::BadMethod()
    {
      static scoped_ptr<Dog> pdog;
      {
         Lock l(Mutex);
         if(!pdog.get())
           pdog.reset(new Dog("Lassie"));
      }
      return pdog.get();
    }
    

答案 1 :(得分:4)

不确定这是否是您的意思,但您可以通过调用pthread_once来删除对POSIX系统的提升依赖性。我想你必须在Windows上做一些不同的事情,但是避免这就是为什么boost首先有一个线程库,以及人们为什么要付出代价的原因。

做任何“线程安全”的事情本质上与你的线程实现有关。你必须依赖某些东西,即使它只是依赖于平台的内存模型。在纯C ++ 03中根本不可能对线程做任何假设,这些线程超出了语言的范围。

答案 2 :(得分:3)

你可以做到的一种方法是不需要互斥锁来保证线程的安全,就是让单例文件成为静态文件,而不是静态函数:

static Dog dog("Lassie");
Dog* MyClass::BadMethod()
{
  return &dog;
}

Dog实例将在主线程运行之前初始化。文件静态变量与初始化顺序有一个着名的问题,但只要Dog不依赖于另一个翻译单元中定义的任何其他静态,这不应该引起关注。

答案 3 :(得分:2)

我所知道的唯一方法是保证您不会遇到像"static Dog"这样的非受保护资源的线程问题,因为要求它们在之前全部实例化创建任何线程。

这可能就像记录在执行任何其他操作之前必须在主线程中调用MyInit()函数一样简单。然后构造MyInit()来实例化并销毁包含其中一个静态的每种类型的一个对象。

唯一的另一种选择是对他们如何使用生成的代码施加另一个限制(使用Boost,Win32线程等)。在我看来,这些解决方案中的任何一个都是可以接受的 - 可以生成他们必须遵循的规则。

如果他们不遵守您的文档规定的规则,那么所有投注都将被取消。他们必须调用初始化函数或依赖于Boost的规则对我来说并不合理。

答案 4 :(得分:2)

AFAIK,唯一一次安全地完成这项工作并且没有互斥体或之前初始化全局实例的是Matthew Wilson的Imperfect C++,它讨论了如何使用“旋转互斥体”来实现这一点。我不接近我的副本,所以现在不能再告诉你了。

IIRC,在STLSoft库中有一些使用它的例子,虽然我现在不记得哪些组件。