C++单例模式实现 _ 饿汉式与懒汉式线程安全详解【源码】

星涛姑娘_1296

星涛姑娘_1296

2026-04-11

449人浏览

原创

饿汉式天生线程安全,因静态实例在main前由单线程完成初始化;懒汉式默认不安全,需双重检查锁(dcl)配合atomic/volatile防重排,仅首次创建同步。

c++单例模式实现 _ 饿汉式与懒汉式线程安全详解【源码】

饿汉式天然线程安全,懒汉式默认不安全;多线程下直接用饿汉最省心,非要懒加载就得加双重检查锁(DCL),且必须用 std::atomicvolatile 防止指令重排。

饿汉式为什么天生线程安全

因为 static 全局对象的初始化发生在 main() 之前,由单线程(程序启动时的初始化阶段)完成。所有线程看到的 m_instance 都是已构造完毕的同一块内存。

  • 无需任何锁,无性能开销
  • 构造函数里做耗时操作(如读配置、连数据库)会拖慢程序启动,但不会引发竞态
  • 静态成员变量必须在类外定义并初始化,例如:Singleton Singleton::m_instance;
  • 如果构造函数抛异常,程序会直接终止(C++ 标准规定静态初始化失败即 std::terminate

懒汉式不加锁一定出问题

典型错误现象是:两个线程同时执行 if (p == nullptr) 判断为真,各自调用 new Singleton,最终生成两个实例——彻底违反单例语义。

C++ 算法竞赛自动化测试数据生成与校验框架
C++ 算法竞赛自动化测试数据生成与校验框架

根据原题生成新题面、验证器及完整测试数据,自动套用 testlib 模板,用于用户要求生成测试数据时。

下载
  • new Singleton 不是原子操作:分配内存 + 调用构造函数 + 写入指针,三步可能被重排
  • 即使加了互斥锁,每次调用 GetInstance() 都锁,性能差(尤其高并发场景)
  • 只在首次创建时需要同步,后续应直接返回指针

懒汉式线程安全的正确写法(DCL)

双重检查锁(Double-Checked Locking)是主流解法,但 C++11 以前极易出错;C++11 起必须配合 std::atomic 保证可见性与顺序性。

  • 指针变量必须声明为 static std::atomic<singleton> p{nullptr}</singleton>,不能用裸指针
  • 第一次检查用 load(std::memory_order_acquire),避免编译器/处理器重排构造动作
  • 加锁后第二次检查不可省略,防止多个线程在锁外都通过第一层判断
  • 构造完成后用 store(p, std::memory_order_release) 确保构造完成对其他线程可见
  • 示例关键片段:
    static std::atomic<singleton> p{nullptr};
    Singleton* Singleton::GetInstance() {
        Singleton* tmp = p.load(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<:mutex> lock(mutex_);
            tmp = p.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton();
                p.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }</:mutex></singleton>

饿汉 vs 懒汉:选哪个取决于初始化时机和资源代价

饿汉强制提前初始化,懒汉延迟但需小心线程模型;没有绝对优劣,只有是否匹配场景。

  • 配置管理、日志器、全局缓存等「启动即需」组件,优先饿汉——简单、安全、零风险
  • 数据库连接池、大内存缓冲区等「按需创建且初始化昂贵」的组件,才考虑带 DCL 的懒汉
  • 注意:C++11 起推荐用局部静态变量实现懒汉(static Singleton instance;),由标准保证线程安全,但仅适用于无参构造且不需在析构时做清理的场景
  • 析构顺序不可控:饿汉实例在 main 结束后销毁,若其他静态对象依赖它,可能访问已析构对象

真正容易被忽略的是:懒汉的 DCL 实现中,std::atomic 的 memory order 选错或漏掉,会导致极难复现的偶发多实例;而饿汉的构造异常会静默终止程序——这两点在线上环境都极难定位。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

3148

10

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

2025.10.23

564

4

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

2025.10.23

654

12

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

2026.04.23

299

31

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

3438

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

989

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

303

30

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

2026.01.21

479

24

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

2026.02.06

329

20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Conan 2 Essentials 免费课程
Conan 2 Essentials 免费课程

共0课时 | 0人学习

CMake 与 Conan 集成实践
CMake 与 Conan 集成实践

共0课时 | 0人学习

Conan 2 高级依赖模型介绍
Conan 2 高级依赖模型介绍

共0课时 | 0人学习