C++怎么实现单例模式双重锁

来源:安卓APP网作者:卡拉米头衔:草根站长
导读:本期聚焦于小伙伴创作的《C++怎么实现单例模式双重锁》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++怎么实现单例模式双重锁》有用,将其分享出去将是对创作者最好的鼓励。

单例模式要求一个类在程序运行期间只能存在一个实例,同时提供全局的访问入口,在配置管理、日志模块等场景中应用十分广泛。多线程环境下,多个线程同时尝试获取单例实例时,普通的非线程安全实现可能会导致实例被多次创建,破坏单例的约束。

C++怎么实现单例模式双重锁

普通单例的线程安全问题

最常见的懒汉式单例实现如下,这种写法在单线程场景下可以正常工作,但在多线程环境下存在风险:

#include <iostream>

class Singleton {
private:
    static Singleton* instance;
    // 私有构造函数,禁止外部直接创建实例
    Singleton() {
        std::cout << "Singleton instance created" << std::endl;
    }

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl; // 单线程下输出1
    return 0;
}

当两个线程同时判断instance == nullptr成立时,都会执行new Singleton()操作,最终会创建出两个不同的实例,违背单例的设计初衷。

双重检查锁定的实现思路

双重检查锁定的核心是在加锁前后分别检查实例是否已经创建,第一次检查是为了避免实例已经存在时仍然加锁带来的性能开销,第二次检查是为了防止多个线程同时通过第一次检查后重复创建实例。实现时需要注意内存序的问题,避免指令重排导致的未定义行为。

基础双重锁实现(C++11之前)

C++11之前的标准没有内存序相关的规范,不同编译器对指令重排的处理不同,下面的实现在部分场景下可能存在问题:

#include <iostream>
#include <pthread.h>

class Singleton {
private:
    static Singleton* instance;
    static pthread_mutex_t mutex;
    Singleton() {
        std::cout << "Singleton instance created" << std::endl;
    }

public:
    static Singleton* getInstance() {
        // 第一次检查,实例已存在则直接返回,避免加锁开销
        if (instance == nullptr) {
            pthread_mutex_lock(&mutex);
            // 第二次检查,防止多个线程同时通过第一次检查后重复创建
            if (instance == nullptr) {
                instance = new Singleton();
            }
            pthread_mutex_unlock(&mutex);
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;
pthread_mutex_t Singleton::mutex = PTHREAD_MUTEX_INITIALIZER;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl;
    return 0;
}

上述代码中instance = new Singleton()可以拆分为三步:分配内存、调用构造函数、将指针指向分配的内存。编译器可能会将第三步重排到第二步之前,此时另一个线程第一次检查instance不为空,会直接返回一个未完成构造的实例,导致程序出错。

C++11及之后的正确实现

C++11引入了内存序相关的特性,使用std::atomicstd::mutex可以写出线程安全且符合标准的双重锁单例:

#include <iostream>
#include <mutex>
#include <atomic>

class Singleton {
private:
    static std::atomic<Singleton*> instance;
    static std::mutex mtx;
    Singleton() {
        std::cout << "Singleton instance created" << std::endl;
    }

public:
    static Singleton* getInstance() {
        // 使用原子操作加载实例指针,避免指令重排问题
        Singleton* tmp = instance.load(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            tmp = instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton();
                // 使用释放语义存储指针,保证构造完成后再被其他线程看到
                instance.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }
};

std::atomic<Singleton*> Singleton::instance(nullptr);
std::mutex Singleton::mtx;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl;
    return 0;
}

这里使用std::atomicmemory_order_acquirememory_order_release来保证内存序:存储实例时使用释放语义,加载实例时使用获取语义,确保其他线程能正确看到实例的完整构造过程,避免指令重排带来的问题。

更简洁的C++11单例实现

实际上C++11之后的标准保证了函数内静态局部变量的初始化是线程安全的,因此可以用更简洁的方式实现线程安全单例,不需要手动处理锁和原子操作:

#include <iostream>

class Singleton {
private:
    Singleton() {
        std::cout << "Singleton instance created" << std::endl;
    }

public:
    static Singleton& getInstance() {
        // 静态局部变量,C++11保证初始化线程安全
        static Singleton instance;
        return instance;
    }
    // 禁止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << (&s1 == &s2) << std::endl;
    return 0;
}

这种实现方式代码更简洁,也不容易出错,是C++11及之后版本实现单例模式的推荐方案。如果需要在不支持C++11的老版本环境中实现线程安全单例,再考虑使用双重检查锁定的方式。

实现注意事项

  • 单例类的构造函数、拷贝构造函数、赋值运算符都需要设为私有或删除,避免外部创建或拷贝实例。
  • 双重检查锁定实现中必须使用原子操作或者内存屏障来避免指令重排问题,否则可能出现未定义行为。
  • 锁的粒度要尽可能小,第一次检查的目的就是减少加锁的频率,提升多线程场景下的性能。
  • 如果单例实例需要销毁,要注意销毁顺序的问题,避免其他单例依赖当前单例时已经被销毁。

C++单例模式双重检查锁定线程安全单例实现修改时间:2026-06-18 22:54:38

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。