单例和多线程

Posted

技术标签:

【中文标题】单例和多线程【英文标题】:Singleton & Multi-threading 【发布时间】:2010-06-18 11:40:12 【问题描述】:

我有以下课程

class Singleton

  private:

    static Singleton *p_inst;
    Singleton();

  public:

    static Singleton * instance()
    
      if (!p_inst)
      
        p_inst = new Singleton();
      

      return p_inst;
    
;

请详细说明在多线程环境中实现 Singleton 时采取的预防措施。

【问题讨论】:

粘贴代码时,请确保使用空格而不是制表符,因为后者会搞砸降价。 查看这里:***.com/questions/1008019/c-singleton-design-pattern/… 我要采取的预防措施是不要实现单例。他们总是比他们的价值更麻烦。 【参考方案1】:

在多线程中那个子句

if(!p_inst)

    p_inst = new Singleton();

实际上是 3 个单独的操作。您正在获取p_inst 的值,设置p_inst 的值并写入p_inst 的值。因此 get-set-write 意味着您需要在 p_inst 周围加锁,否则您可以有 2 个线程创建每个线程使用的 Singleton 值。

以下是查看问题的方法,假设您的 Singleton 有一个可变字段 val

thread A -> p_inst is NULL
    thread B -> p_inst is NULL
       thread A -> set to Singleton (1)
           thread B -> set to Singleton (2)
              thread C -> p_inst is Singleton (2)
                  thread A -> set val to 4
                      thread B -> set val to 6
                         thread C -> get val (it's 6)
                             thread A -> get val (it's 4!!)

你看到了吗?有 2 个 Singleton 的副本漂浮在周围,它们都不知道另一个。检查Singleton 的第三个线程只会看到最后一个分配。但是通过锁定,您可以防止多重分配和这些类型的问题。

【讨论】:

是这样的吗?静态 Singleton* instance() if ( ! p_inst ) Mutex m; m.lock(); // 临界区 if ( !p_inst ) p_inst = new Singleton(); // 结束临界区 m.unlock(); 返回 p_inst; 我知道通过使用互斥锁并锁定它会成为一种缓慢的设计模式,但是有什么办法可以优化它吗? @ronan:是的,不要使用这种“单例”的反模式。 您可以只使用静态变量而不是指针。这不是“懒惰”,但它会避免你的建设问题。另一方面,除非您知道自己在做什么,否则永远不要使单例可变。将其保留给不受您的应用程序控制的 Socket 池、线程池和其他依赖于操作系统的资源。 @ronan:本文解释了为什么您建议的方法不安全。 drdobbs.com/184405726【参考方案2】:

在分配或读取指针之前,您必须使用互斥锁并锁定指针,这使得这是一种缓慢(而且 imo 简直是糟糕透顶)的设计模式。

【讨论】:

【参考方案3】:

我会简短:这取决于您的编译器。

如果您的编译器为本地静态(即嵌入在方法中的静态实例)实现多线程同步,则使用它并完成它。 如果不是,Herb Sutter 证明这是不可能的。

现在,你必须意识到你可能不需要这个。

有两种方法可以解决这个问题,不需要任何多线程意识。

    只需使用static 实例而不是动态分配它。安全简单。如果您需要从另一个 static 变量访问它,可能会导致初始化顺序出现问题 在拥有多个线程之前创建单例实例。通常的技巧是从main 调用它。

当然,真正的问题是:您不能只传递对对象的引用而不是创建全局变量吗?这将使测试更容易;)

【讨论】:

【参考方案4】:

您可以通过在启动多个线程之前简单地分配(您选择的任何方式)此类对象来消除所有问题。由于设计限制(在静态中使用单例,您需要延迟分配等),这可能并不总是可行,但它很简单,并且可以让您控制创建顺序。有时跟踪与此类对象的分配顺序和时间有关的问题是您可以轻松避免的麻烦。

附: - 我知道这并不能直接回答您的问题,但它可能是解决实际问题的实用解决方案,并不复杂。

【讨论】:

【参考方案5】:

对于多线程构造,在 instance() 函数中使用静态变量。静态变量的初始化由编译器自动保护。任何其他操作都需要显式锁定。使用互斥锁。

class Singleton

  private:

    Singleton();

  public:

    static Singleton * instance()
    
      static Singleton inst;
      return &inst;
    
;

【讨论】:

我必须明白我不能使用关键字 volatile 吗?它有什么帮助? C++0x 之前的标准不保证方法中静态变量的初始化不受竞争条件的影响。某些 C++ 编译器可能会确保这一点安全,但很多则不然。 这是错误的 pre-c++0x。另一位评论者已经提到了这一点,但我会明确说明。【参考方案6】:

您应该问问自己,线程安全是什么意思。

你的单例真的需要线程安全吗?

如果没有,请考虑线程静态方法

您想保证不会创建两个单例实例吗?

如果不是,您的上述解决方案可能没问题,没有任何锁定:您在构建时遇到了竞争条件 - 但您不在乎,因为最终只有一个人能够幸存 -但是,除非您小心,否则您可能会遇到资源泄漏,这可能很重要,也可能不重要。 (这本质上是一个缓存)。

您想保证最终只剩下一个实例吗?

你关心锁定成本吗?

如果不是(这很常见),你可以把它锁起来,然后开心。

单例模式是一种可以解决各种问题的模式——但是需要什么样的线程安全与单例模式本身无关,而与你想要它的目的有关。

【讨论】:

- 感谢您的解释。我必须在这里知道静态线程方法,基本上我希望 Singleton 是线程安全的。

以上是关于单例和多线程的主要内容,如果未能解决你的问题,请参考以下文章

架构师养成记--6.单例和多线程ThreadLocal

spring使用单例 线程怎么解决并发

Java 单例和同步

简单多线程和单例学习例一

既然spring中注入用单例,为了解决多线程安全问题,还得用theardlocal为每个线程创建共

4创建型模式之单例模式__多线程下的懒汉式单例和饿汉式单例