双重检查锁定(Double-Checked Locking)


"双重检查锁定"(Double-Checked Locking)是一种用于提高多线程环境下性能的设计模式,主要用于懒初始化(lazy initialization)场景。它确保了在多线程情况下,某个资源(如单例实例)只被初始化一次,并且在初始化后访问时无需加锁,从而减少不必要的锁开销。

双重检查锁定的工作原理

双重检查锁定的核心思想是,在对某个共享资源进行访问时,首先在锁外进行一次检查,如果不满足条件(例如资源尚未初始化),才在锁内进行第二次检查,并执行初始化操作。这种方式可以避免每次访问资源时都进行加锁操作,降低锁带来的性能开销。

经典的双重锁定示例

以下是一个典型的双重检查锁定模式的实现示例,通常用于单例模式:

cpp 复制代码
class Singleton {
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {               // 第一次检查
            std::lock_guard<std::mutex> lock(mutex_);
            if (instance == nullptr) {           // 第二次检查
                instance = new Singleton();      // 懒初始化
            }
        }
        return instance;
    }

private:
    Singleton() = default;
    ~Singleton() = default;

    static Singleton* instance;
    static std::mutex mutex_;
};

// 静态成员变量定义
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;

工作流程

  • 第一次检查: 在 getInstance() 方法中,首先检查 instance 是否为空。如果 instance 已经被初始化,直接返回,不需要加锁。
  • 加锁: 如果 instance 为空,表示尚未初始化,此时进入临界区,加锁确保只有一个线程可以执行接下来的初始化代码。
  • 第二次检查: 在加锁之后,再次检查 instance 是否为空。这是因为可能有多个线程在第一次检查时同时通过并进入临界区,而此时只有第一个进入临界区的线程需要进行初始化,其他线程需要跳过初始化操作。
  • 初始化: 如果 instance 仍为空,执行初始化操作。
  • 返回实例: 无论 instance 是否已经初始化,最后都返回该实例。

双重检查锁定存在的问题与解决方案

1、内存模型问题

在某些早期的编译器或平台上,双重检查锁定可能会由于内存模型的原因导致未定义行为。例如,编译器或处理器可能会对代码进行重排序,导致其他线程看到一个部分构造的对象(即,内存已经分配,但构造函数尚未执行完毕),从而产生问题。

2、C++11及以上的解决方案

C++11 引入了更强的内存模型,并提供了 std::atomic 类型和 std::call_once 等工具,帮助开发者更安全地实现懒初始化和双重检查锁定。

  • 使用 std::atomic: 可以通过使用 std::atomic 来保证对 instance 的检查是原子的,避免由于编译器或硬件层面的优化导致的重排序问题。
  • 使用 std::call_once: C++11 提供的 std::call_once 和 std::once_flag 能够保证某个操作只执行一次,而且是线程安全的,通常可以用来替代双重检查锁定。
cpp 复制代码
#include <mutex>

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(initFlag, []() { instance.reset(new Singleton); });
        return *instance;
    }

private:
    Singleton() = default;
    ~Singleton() = default;

    static std::unique_ptr<Singleton> instance;
    static std::once_flag initFlag;
};

// 静态成员变量定义
std::unique_ptr<Singleton> Singleton::instance;
std::once_flag Singleton::initFlag;

在这个实现中,std::call_once 确保 Singleton 的初始化只执行一次,并且是线程安全的。这样可以避免双重检查锁定中的内存模型问题,也使代码更简洁。

总结

双重检查锁定是一种在多线程环境中用于懒初始化的优化方法,能够减少不必要的锁开销。虽然传统的双重检查锁定模式在某些情况下存在内存模型问题,但通过 C++11 提供的新特性如 std::atomic 和 std::call_once,我们可以更安全地实现这一模式,并确保线程安全性。


相关推荐
春日见10 小时前
win11 分屏设置
java·开发语言·驱动开发·docker·单例模式·计算机外设
短剑重铸之日11 小时前
《设计模式》第二篇:单例模式
java·单例模式·设计模式·懒汉式·恶汉式
钦拆大仁2 天前
Java设计模式-单例模式
java·单例模式·设计模式
老骥伏枥~2 天前
VB.NET 中的单例模式
单例模式·.net
「QT(C++)开发工程师」2 天前
C++ 多种单例模式
java·c++·单例模式
一路往蓝-Anbo3 天前
第 2 篇:单例模式 (Singleton) 与 懒汉式硬件初始化
开发语言·数据结构·stm32·单片机·嵌入式硬件·链表·单例模式
亓才孓3 天前
[设计模式]单例模式的懒汉式写法
单例模式·设计模式
亓才孓4 天前
[设计模式]单例模式饿汉式写法
单例模式·设计模式
春日见4 天前
C++单例模式 (Singleton Pattern)
java·运维·开发语言·驱动开发·算法·docker·单例模式
Hx_Ma164 天前
单例模式实例
单例模式