0

0

C++ 单例模式中的双重检查锁定是什么?(如何在多线程下安全实现)

穿越時空

穿越時空

发布时间:2026-02-27 16:19:32

|

617人浏览过

|

来源于php中文网

原创

推荐用 std::call_once + static 局部变量替代手写双重检查锁定,因其线程安全、无需手动管理内存序、异常安全且编译器优化为无锁路径;手写 dcl 易因内存序错误或类型失配导致偶发崩溃。

c++ 单例模式中的双重检查锁定是什么?(如何在多线程下安全实现)

双重检查锁定为什么需要 std::atomic 和内存序

不加内存序的双重检查锁定在多线程下可能返回未初始化的对象,根本原因是编译器重排和 CPU 乱序执行。比如 instance = new Singleton() 实际包含三步:分配内存、调用构造函数、将地址写入静态指针——后两步可能被交换,导致其他线程看到非空但未构造完成的指针。

必须用 std::atomic 包裹指针,并指定 memory_order_acquire(读)和 memory_order_release(写),否则无法阻止重排。C++11 之前靠 volatile 是无效的,它不提供跨线程同步语义。

  • std::atomic<singleton> instance{nullptr}</singleton> 是底线,不能用裸指针
  • 第一次检查用 load(std::memory_order_acquire),第二次写入用 store(ptr, std::memory_order_release)
  • 构造函数内不能有耗时操作或抛异常,否则 store 不会执行,下次调用仍会重复尝试构造

为什么推荐用 std::call_once + static 局部变量

手写双重检查锁定容易漏掉内存序、忘记原子操作、或在异常路径中留下竞态。而 C++11 起,static 局部变量的首次初始化本身就是线程安全的,背后由 std::call_once 保证,且无需手动管理内存序。

它比手写 DCL 更简洁、更难出错,且主流编译器(GCC/Clang/MSVC)都已优化为无锁路径(首次之后不进锁)。

立即学习C++免费学习笔记(深入)”;

Illustroke
Illustroke

text to SVG,AI矢量插画生成工具

下载
  • 写法就是:static Singleton instance; 放在函数里,直接 return instance;
  • 构造函数抛异常也没问题,标准保证:若初始化失败,下次调用仍会重试
  • 不适用于需要延迟构造参数、或需控制析构时机的场景(比如单例依赖全局资源释放顺序)

手写 DCL 时最容易踩的坑

90% 的错误不是逻辑错,而是类型和内存序失配。比如把 std::atomic<singleton></singleton> 写成 std::atomic<singleton></singleton>,或者用 relaxed 序代替 acquire/release

另一个高频问题是“假成功”:代码能编译、能跑通,但在某些 CPU 架构(如 ARM)或高并发压测下才暴露崩溃,因为 relaxed 序在 x86 上看似没问题,但 ARM 不保证 StoreLoad 顺序。

  • 别用 volatile Singleton* 替代 std::atomic<singleton></singleton>
  • 两次 load() 必须都带 memory_order_acquire,不能第一次用 relaxed
  • 别在构造函数里调用虚函数或访问其他未初始化的单例——此时对象尚未完全构造

什么时候不该用单例 + DCL

如果单例对象需要按特定顺序初始化或销毁(比如 A 依赖 B,B 又依赖 A),DCL 无法控制初始化顺序,static 局部变量也只保证本函数内首次调用时初始化,跨 TU 的顺序仍是未定义的。

还有生命周期问题:进程退出时,静态对象析构顺序与构造顺序相反,但 DCL 创建的对象是堆上分配的,不会自动析构——得额外加 atexit 或手动管理,反而增加复杂度。

  • 优先考虑依赖注入,而不是全局可访问的单例
  • 若必须全局状态,用 thread_local 替代,避免锁和内存序问题
  • 日志、配置等看似适合单例的场景,其实更适合传参或通过 context 对象流转
实际写的时候,static 局部变量那行最短,也最不容易错;手写 DCL 看似可控,但每个原子操作的序、每个指针的生命周期、每个异常分支,都得同时盯住——稍一松懈就埋下偶发崩溃的种子。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

71

2025.10.23

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

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

71

2025.10.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

427

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

599

2023.08.10

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

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

721

2023.08.10

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

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

371

2025.12.24

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

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

27

2026.01.21

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

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

25

2026.01.21

Golang 并发编程模型与工程实践:从语言特性到系统性能
Golang 并发编程模型与工程实践:从语言特性到系统性能

本专题系统讲解 Golang 并发编程模型,从语言级特性出发,深入理解 goroutine、channel 与调度机制。结合工程实践,分析并发设计模式、性能瓶颈与资源控制策略,帮助将并发能力有效转化为稳定、可扩展的系统性能优势。

2

2026.02.27

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 10.3万人学习

C 教程
C 教程

共75课时 | 5万人学习

C++教程
C++教程

共115课时 | 19.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号