前言

在设计模式中,单例模式是最简单的一种。如何确保让一个资源在使用中只能实例化一次呢?如何确保在多线程环境下是线程安全的呢?本文将从最简单的单例到线程安全的单例一一讲解。

一、单线程环境

以下是最起初的单例模式,声明一个静态实例,每次获取前先判断其是否创建,如果没有就创建,如果有就直接返回创建的对象。

class Singlton{    单例模式下,不能使用拷贝构造、复制构造等函数,所以应将其声明为私有的。    c++11 提供delete 关键字,用于删除不适用的构造函数。private:    Singlton(){};    Singlton(const Singlton& obj);    Singleton& operator=(const Singleton&)=delete;static Singlton* instance;public:    Singlton * Singlton::getInstance(){if(instance == null)            instance = new Singlton();return instance;    }};

二、多线程环境(锁代价过高)

由于上述代码只能运行在单线程环境,在多线程中应该使用锁来实现。

class Singlton{private:    Singlton(){};    Singlton(const Singlton& obj);    Singleton& operator=(const Singleton&)=delete;static std::mutex mx;static Singlton* instance;public:    Singlton * Singlton::getInstance(){std::lockguard<std::mutex> lock(mx); if(instance == null)            instance = new Singlton();return instance;    }};

三、多线程环境双检查锁(reorder现象)

由于加锁会使得锁代价过高,每个线程都会阻塞在 mutex 处,直到获取实例。那么就提出了下面这种双检查锁,先判断当前是否有实例化,如果有直接返回,避免了等待所的过程。

class Singlton{private:    Singlton(){};    Singlton(const Singlton& obj);    Singleton& operator=(const Singleton&)=delete;static std::mutex mx;static Singlton* instance;public:    Singlton * Singlton::getInstance(){if(instance == null){                            1std::lockguard<std::mutex> lock(mx);         2if(instance == null)                        3                instance = new Singlton();                4        }return instance;    }};//因为在多线程下,只是对instance变量的读判断,而读是不需要加锁的,//在第一层检查时,如果另外一个线程读取instance不是null,就直接返回。

问题:因为现代编译器和 CPU 都会进行指令优化重排序,比如一句简单的指令                                    instance = new Singlton()程序员人为理解: 1、分配内存   2、调用类构造器初始化 3、将内存指针赋值给 instance;但 CPU 执行指令可能会是:1  -->  3  -->  2 的情况,这种现象将 reorder 重排序;那么当线程 t1 执行 3 的时候,线程 t2 判断 instance 不为空,直接返回,卧槽,使用一个为分配好的内存,灾难是无法想象的。

三、 解决多线程安全的单例

为了解决上述 reorder 现象,是能使用内存 fence 去组织编译器进行重排序,那么这将是真正意义上的线程安全的单例模式。

std::atomic Singlton::m_instance;std::mutex Singlton::m_mutex;Singlton* Singlton::getInstance(){    Singlton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);if(tmp == nullptr){std::lock_guard<std::mutex> lock(m_mutex);        tmp = m_instance.load(std::memory_order_relaxed);if(tmp == nullptr){            tmp = new Singlton;std::amotic_thread_fence(std::memory_order_release);            m_instance.store(tmp, std::memory_order_relaxed);        }    }return tmp;}

四、c++11的线程安全的单例

c++11中引入了 call_once ,这样就可以编写更加简单的单例模式了,以下是 c++11 的线程安全的单例模式。

#include #include #include class Singleton {public:static Singleton& GetInstance() {static std::once_flag s_flag;std::call_once(s_flag, [&]() {      instance_.reset(new Singleton);    });return *instance_;  }  ~Singleton() = default;private:  Singleton() = default;  Singleton(const Singleton&) = delete;  Singleton& operator=(const Singleton&) = delete;private:static std::unique_ptr instance_;};std::unique_ptr Singleton::instance_;

五、工程中那种好?

其实,需要根据项目的不同,应该选择适合自己的单例模式。注:在项目中,一般可将所有的单例在main函数中进行初始化,所有的以后操作都是读操作,既保证线程安全的单例模式

c语言中如何确保一个程序是单例的_c++单例模式相关推荐

  1. C语言中编写一个程序,提示用户输入两个日期,然后显示哪一个日期更早

    文章目录 编写一个程序,提示用户输入两个日期,然后显示哪一个日期更早 #include<stdio.h> int main(){ int a,s,d; int z,x,c; scanf(& ...

  2. java数组输入一个实数_用java!!输入五个数,保存到一个数组中,然后将... C语言,编写一个程序,从键盘输入5个数,算出总和......

    导航:网站首页 > 用java!!输入五个数,保存到一个数组中,然后将... C语言,编写一个程序,从键盘输入5个数,算出总和... 用java!!输入五个数,保存到一个数组中,然后将... C ...

  3. C语言中打开一个应用程序可以调用或运行命令

    C语言中打开一个应用程序可以调用或运行命令system(),也可以调用操作系统的API函数,比如Windows系统可以调用CreateProcess().ShellExecuteEx()等函数来打开一 ...

  4. c语言算正方形面积和周长,c语言中编写一程序计算正方形的周长和面积

    C语言 编写程序,从键盘输入一个正数,计算该数的平方根. #include#includeintmain(){\x09doublex;\x09scanf("%lf",&x) ...

  5. c语言程序设计 徐庆生,C语言中循环结构程序课的教学设计与探讨.doc

    C语言中循环结构程序课的教学设计与探讨 摘要:循环结构的程序设计是面向过程的程序设计课程的核心部分,掌握好循环结构的程序设计技术对学好此类课程至关重要.本文重点介绍了"C语言程序设计&quo ...

  6. Java黑皮书课后题第5章:**5.45(统计:计算平均值和标准方差)在商务应用中……编写一个程序,提示用户输入10个数字,然后运用下面的公式,显示这些数字的平均值以及标准方差

    5.45(统计:计算平均值和标准方差)在商务应用中--编写一个程序,提示用户输入10个数字,然后运用下面的公式,显示这些数字的平均值以及标准方差 题目 题目描述 破题 运行示例 代码 题目 题目描述 ...

  7. c语言判断字符串的编码,C语言中判断一个char*是不是utf8编码

    C语言中判断一个char*是不是utf8编码 里我修改了一下, 纯ASCII编码的字符串也返回true, 因为UTF8和ASCII兼容 实例代码: int utf8_check(const char* ...

  8. 怎样设置一个函数C语言,C语言中怎样编写一个函数 如何在C语言中定义一个函数?...

    如何在C语言中定义一个函数?小编很想在你面前流泪最后却还是选择装作打个哈欠 为什么小编怎么定义函数都不正确呢? 总是说小编 表达语法错误在main函数中 小编们可以在头文件与main函数之间定义,并编 ...

  9. c语言判断utf-8中文字符串,C语言中判断一个char*是不是utf8编码分享

    --想了解C语言中判断一个char*是不是utf8编码分享的全部内容且更多的C语言教程关注 C语言中判断一个char*是不是utf8编码 里我修改了一下, 纯ASCII编码的字符串也返回true, 因 ...

最新文章

  1. 解决:angularjs radio默认选中失效问题
  2. Nature:AI为什么总是歧视重重?
  3. 《深入浅出WPF》笔记——事件篇
  4. php嵌入html后缀_php中怎么嵌入html代码
  5. C++——《数据结构与算法》实验——排序算法的实现
  6. Tomcat集群快速入门:Nginx负载均衡配置,常用策略,场景及特点
  7. @RequestMapping和@GetMapping @PostMapping 区别
  8. mysql空间是什么格式_MySQL数据类型 - 空间数据类型 (6)
  9. python静默打印pdf_前端静默打印实现 html pdf集合
  10. 人工智能、机器学习和深度学习的区别与认识
  11. 安卓3d游戏开发引擎_鲁大师安卓3D引擎更新,跑分测试精准度再升级
  12. 利用模态DIV结合UpdateProgress防止页面重复提交
  13. mysqldump 工具的使用
  14. filepath直接指定到文件名吗_按照txt中指定的文件名,从src_path中拷贝文件到dest_path(copyfile_from_txt)...
  15. 读懂电影专业名词(转自CMCT-PT)
  16. 关于GitHub Education(GitHub教育认证)认证
  17. 贝塞尔曲线 unity两点画曲线弧线三点
  18. indigo版本teb_local_planner常见编译问题
  19. 科目三必看要点 驾驶经验汇总
  20. U盘、打印机泄密的隐患

热门文章

  1. Docker 三剑客之 Docker Compose
  2. mysql_connect报告”No such file or directory”错误的解决方法
  3. [笔记]TB-6S-LX150T-IMG2_HWUserManual_1.02e实例讲解
  4. 驾考通专业版2011
  5. 07:有趣的跳跃【一维数组】
  6. apt-get install php5-redis,Ubuntu14-04安装redis和php5-redis扩展
  7. 10a 16a 插座区别_电动汽车小知识(NO·5):电动汽车能否用家里的插座进行充电?...
  8. 华为交换机模拟器_从零开始学习华为路由交换 | 配置缺省静态路由
  9. git安装步骤_详解linux安装git的方法步骤(超实用)
  10. Linux linux下的进程状态