【问题标题】:Thread-safe static variables without mutexing?没有互斥的线程安全静态变量?
【发布时间】:2009-06-27 05:30:49
【问题描述】:

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

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

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

有没有办法让我做到这一点,而无需对最终用户施加额外的依赖?

【问题讨论】:

  • 您还可以将 boost 库中的静态链接到您自己的库中,然后您可以随意使用 boost,而不必担心给您的用户带来不便。
  • @LucianAdrianGrijincu,这个问题早于 C++11(它发布于 2009 年),所以虽然你的问题是相关的,但它并不是一个严格的重复。感谢您的链接。

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


【解决方案1】:

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

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

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

  1. 不要偷懒(加载):在静态初始化期间进行初始化。如果另一个静态函数在其构造函数中调用此函数,则可能会出现问题,因为静态初始化的顺序未定义(请参阅here)。
  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();
}

【讨论】:

【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • 我记得如果您在 Windows 上的 DLL 中有全局初始化程序,可能会出现问题——但我不记得具体细节,而且只有当代码在 DLL 中时它才真正相关:D
    • 我的代码是否驻留在 DLL 中:) 我应该注意哪些问题?
    • 如果我知道的话该死。我最好的猜测是每次加载 dll 时构造函数都会运行一次,所以如果 dll 被卸载并重新加载,它将被重新创建,所以它有点打破单例“合同”,但你无能为力
    • 我过去处理它的方式是使用函数范围的全局变量,就像你最初所做的那样,但显然存在种族问题:-/
    【解决方案4】:

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

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

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

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-11-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-09
        • 2014-12-24
        相关资源
        最近更新 更多