13

我记得读过在方法中声明的静态变量不是线程安全的。(参见Todd Gardner提到的 Meyer 的单身呢?

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

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

有没有办法让我做到这一点,而不需要对最终用户产生额外的依赖?

4

5 回答 5

10

你是对的,这样的静态初始化不是线程安全的(这里有一篇文章讨论编译器会将它变成什么)

目前,没有标准的、线程安全的、可移植的方式来初始化静态单例。可以使用双重检查锁定,但您可能需要不可移植的线程库(请参阅此处的讨论)。

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

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

示例 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();
}
于 2009-06-27T05:55:01.730 回答
4

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

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

于 2009-06-27T09:55:33.263 回答
3

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

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

Dog实例将在主线程运行之前初始化。文件静态变量在初始化顺序上有一个著名的问题,但只要 Dog 不依赖于另一个翻译单元中定义的任何其他静态变量,就不必担心这一点。

于 2009-06-27T05:47:05.190 回答
2

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

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

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

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

于 2009-06-27T05:43:45.367 回答
2

AFAIK,唯一一次安全且没有互斥锁或全局实例初始化的情况是在 Matthew Wilson 的Imperfect C++中,它讨论了如何使用“自旋互斥锁”来做到这一点。我离我的副本不近,所以现在不能更准确地告诉你。

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

于 2009-06-27T06:36:51.553 回答