C++并发编程(6):单例模式、once_flag与call_once、call_once实现单例

单例模式

参考博客

【C++】单例模式(饿汉模式、懒汉模式)

C++单例模式总结与剖析

饿汉单例模式 C++实现

C++单例模式(饿汉式)

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结
,一共有23种经典设计模式

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性

设计模式使代码编写真正工程化,设计模式是软件工程的基石脉络,如同大厦的结构一样

单例模式是设计模式中最常用的一种模式,一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享

基础要点:

  • 全局只有一个实例:static 特性,同时禁止用户自己声明并定义实例(把构造函数设为 private)
  • 线程安全
  • 禁止赋值和拷贝
  • 用户通过接口获取实例:使用 static 类成员函数

单例的实现主要有饿汉式和懒汉式两种,分别进行介绍

饿汉式

不管你将来用不用,程序启动时就创建一个唯一的实例对象

优点:简单

缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定

示例代码:

//  Hunger_Singleton_pattern
//  Created by lei on 2022/05/13#include <iostream>
#include <memory>
using namespace std;class Example
{
public:typedef shared_ptr<Example> Ptr;static Ptr GetSingleton(){cout << "Get Singleton" << endl;return single;}void test(){cout << "Instance location:" << this << endl;}~Example() { cout << "Deconstructor called" << endl; };private:static Ptr single;Example() { cout << "Constructor called" << endl; };Example &operator=(const Example &examp) = delete;Example(const Example &examp) = delete;
};
Example::Ptr Example::single = shared_ptr<Example>(new Example);int main()
{Example::Ptr a = Example::GetSingleton();Example::Ptr b = Example::GetSingleton();a->test();b->test();cout << "main end" << endl;return 0;
}

打印输出:

Constructor called
Get Singleton
Get Singleton
Instance location:0x55d43c9dce70
Instance location:0x55d43c9dce70
main end
Deconstructor called

可以看到拷贝构造函数只调用了一次,并且两个对象内存地址相同,说明该类只能实例化一个对象

饿汉单例模式的静态变量的初始化由C++完成,规避了线程安全问题,所以饿汉单例模式是线程安全的

在大多数情况下使用饿汉单例模式是没有问题的

有缺陷的懒汉模式

懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用get_instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存

//  Defect_Lazy_Singleton_pattern
//  Created by lei on 2022/05/13#include <iostream>
#include <thread>
using namespace std;class Singleton
{
private:Singleton(){cout << "constructor called!" << endl;}Singleton(const Singleton &) = delete;Singleton& operator=(const Singleton &) = delete;static Singleton *m_instance_ptr;public:~Singleton(){cout << "destructor called!" << endl;}static Singleton *get_instance(){if (m_instance_ptr == nullptr){m_instance_ptr = new Singleton;}return m_instance_ptr;}void use() const { cout << "in use" << endl; }
};Singleton *Singleton::m_instance_ptr = nullptr;     //静态成员变量类内声明类外初始化int main()
{Singleton *instance = Singleton::get_instance();Singleton *instance_2 = Singleton::get_instance();// thread t1(Singleton::get_instance);// thread t2(Singleton::get_instance);// thread t3(Singleton::get_instance);// thread t4(Singleton::get_instance);// t1.join();// t2.join();// t3.join();// t4.join();return 0;
}

打印输出:

constructor called!

取了两次类的实例,却只有一次类的构造函数被调用,表明只生成了唯一实例,这是个最基础版本的单例实现,存在以下问题

1、当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_instance_ptr
是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_instance_ptr
还是空的,于是也开始实例化单例;这样就会实例化出两个对象

2、类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用,因此会导致内存泄漏

改进的懒汉模式

对应上面两个问题,有以下解决方法:

1、用mutex加锁

2、使用智能指针

//  Improve_Lazy_Singleton_pattern
//  Created by lei on 2022/05/13#include <iostream>
#include <memory> // shared_ptr
#include <mutex>  // mutex
#include <thread>
using namespace std;class Singleton
{
public:typedef shared_ptr<Singleton> Ptr;~Singleton(){cout << "destructor called!" << endl;}Singleton(const Singleton &) = delete;Singleton& operator=(const Singleton &) = delete;static Ptr get_instance(){// "double checked lock"if (m_instance_ptr == nullptr){lock_guard<mutex> lk(m_mutex);if (m_instance_ptr == nullptr){m_instance_ptr = shared_ptr<Singleton>(new Singleton);}}return m_instance_ptr;}private:Singleton(){cout << "constructor called!" << endl;}static Ptr m_instance_ptr;static mutex m_mutex;
};// initialization static variables out of class
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
mutex Singleton::m_mutex;int main()
{Singleton::Ptr instance = Singleton::get_instance();Singleton::Ptr instance2 = Singleton::get_instance();// thread t1(Singleton::get_instance);// thread t2(Singleton::get_instance);// thread t3(Singleton::get_instance);// thread t4(Singleton::get_instance);// t1.join();// t2.join();// t3.join();// t4.join();return 0;
}

打印输出:

constructor called!
destructor called!

只构造了一次实例,并且发生了析构

缺陷是双检锁依然会失效,具体原因可以看下面的文章

https://www.drdobbs.com/cpp/c-and-the-perils-of-double-checked-locki/184405726

推荐的懒汉模式

//  Recommand_Lazy_Singleton_pattern
//  Created by lei on 2022/05/13#include <iostream>
#include <thread>
using namespace std;class Singleton
{
public:~Singleton(){cout << "destructor called!" << endl;}Singleton(const Singleton &) = delete;Singleton &operator=(const Singleton &) = delete;static Singleton &get_instance(){static Singleton instance;return instance;}private:Singleton(){cout << "constructor called!" << endl;}
};int main()
{Singleton &instance_1 = Singleton::get_instance();Singleton &instance_2 = Singleton::get_instance();// thread t1(Singleton::get_instance);// thread t2(Singleton::get_instance);// thread t3(Singleton::get_instance);// thread t4(Singleton::get_instance);// t1.join();// t2.join();// t3.join();// t4.join();return 0;
}

打印输出:

constructor called!
destructor called!

这种方法又叫做 Meyers’ Singleton Meyer’s的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束

这是最推荐的一种单例实现方式:

  • 通过局部静态变量的特性保证了线程安全
  • 不需要使用共享指针,代码简洁
  • 注意在使用的时候需要声明单例的引用 Single& 才能获取对象

once_flag与call_once

参考博客

C++11于once flag,call_once:分析的实现

C++11实现线程安全的单例模式(使用std::call_once)

在多线程编程中,有一个常见的情景是某个任务仅仅须要运行一次

在C++11中提供了非常方便的辅助类once_flag与call_once

once_flag和call_once的声明:

struct once_flag
{constexpr once_flag() noexcept;once_flag(const once_flag&) = delete;once_flag& operator=(const once_flag&) = delete;
};
template<class Callable, class ...Args>void call_once(once_flag& flag, Callable&& func, Args&&... args);}  // std

简单示例:

//  once_flag and call_once simple example
//  Created by lei on 2022/05/13#include <iostream>
using namespace std;once_flag flag;void do_once()
{call_once(flag, [&](){ cout << "Called once" << endl; });
}int main()
{std::thread t1(do_once);std::thread t2(do_once);std::thread t3(do_once);std::thread t4(do_once);t1.join();t2.join();t3.join();t4.join();
}

打印输出:

Called once

可以看到4个线程只执行了一次do_once( )函数

call_once实现单例模式

//  Call_once_Singleton_pattern
//  Created by lei on 2022/05/13#include <iostream>
#include <thread>
#include <mutex>
using namespace std;once_flag cons_flag;class A
{
public:typedef shared_ptr<A> Ptr;void m_print() { cout << "m_a++ = " << ++m_a << endl; }static Ptr getInstance(int a){cout << "Get instance" << endl;if (m_instance_ptr == nullptr){lock_guard<mutex> m_lock(m_mutex);if (m_instance_ptr == nullptr){call_once(cons_flag, [&](){ m_instance_ptr.reset(new A(a)); });}}return m_instance_ptr;}~A(){cout << "Deconstructor called" << endl;}private:static mutex m_mutex;int m_a;static Ptr m_instance_ptr;A(int a_) : m_a(a_){cout << "Constructor called" << endl<< "m_a = " << m_a << endl;}A &operator=(const A &A_) = delete;A(const A &A_) = delete;
};
A::Ptr A::m_instance_ptr = nullptr;
mutex A::m_mutex;void test(int aa)
{cout << "Go in test..." << endl;A::Ptr tp = A::getInstance(aa);cout << "tp location:" << tp << endl;tp->m_print();cout << endl;
}int main()
{thread t1(test, 1);thread t2(test, 2);thread t3(test, 3);thread t4(test, 4);t1.join();t2.join();t3.join();t4.join();cout << "main end..." << endl;return 0;
}

打印输出:

Go in test...
Get instance
Constructor called
m_a = 4
tp location:0x7fd964000f30
m_a++ = 5Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 6Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 7Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 8main end...
Deconstructor called

看到构造函数只调用了一次,并且类A实例化对象的地址始终相同

上面的两个示例程序中都用到了lambda表达式,call_once通常结合lambda一起使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/2955.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

行为型模式 - 策略模式

概述 先看下面的图片&#xff0c;我们去旅游选择出行模式有很多种&#xff0c;可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。 作为一个程序猿&#xff0c;开发需要选择一款开发工具&#xff0c;当然可以进行代码开发的工具有很多&#xff0c;可以选择Idea进行开发&…

Web 3.0时代,重塑教育与学习方式的可能性

随着科技的快速发展和互联网的普及&#xff0c;教育领域也面临着巨大的机遇和挑战。Web 3.0时代的到来为教育与学习方式带来了全新的可能性。在这个数字化时代&#xff0c;我们可以探索和利用Web 3.0技术&#xff0c;重塑教育的方式&#xff0c;提供更个性化、互动性和灵活性的…

在Illustrator中创建 3D 冰淇淋模型对象

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 一旦你学会了如何在Illustrator中制作一个对象3D&#xff0c;你可以前往Envato Elements&#xff0c;在那里你可以找到大量的3D设计来激发你的灵感。这个基于订阅的市场拥有超过 2&#xff0c;000 个 Illus…

php实现站群软件权限管理功能示例

1.管理员页面RBAC.php <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>权限管理</title> <script src"bootstrap/js/jquery-1.11.2.min.js"></script> </head>…

项目名称:智能家居边缘网关项目

一&#xff0c;项目介绍 软件环境: C语言 硬件环境: STM32G030C8TX单片机开发板 开发工具: Linux平台GCC交叉编译环境以及ukeil (1)边缘网关概念 边缘网关是部署在网络边缘侧的网关&#xff0c;通过网络联接、协议转换等功能联接物理和数字世界&#xff0c;提供轻量化的联接管…

通过new FormData提交简单数据

通过new FormData提交简单数据 效果示例图代码 效果示例图 代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style type"text/css">* {padding: 0px;margin: 0px;box-sizing: border-…

SpringBoot整合PostgreSQL教程

主要描述如何优雅的整合postgresql。本文略去如何安装pgsql的过程&#xff0c;详情可参考其他文章。 文章目录 postgresql简介整合postgresql整合mybatis整合mybatis-plus postgresql简介 与mysql一样也是开源的关系型数据库&#xff0c;同时还支持NoSql的文档型存储。在某些方…

费尔法克斯水务通过使用 Liquid UI 移动化和定制 SAP PM 来提高收入和数据完整性

背景 费尔法克斯水务是北弗吉尼亚州地区领先的水县。它是华盛顿特区大都会区的三大供水商之一。它每天为近171万居民提供2.<>亿加仑的水。它渴望坚持其愿景&#xff0c;即保持以客户为中心&#xff0c;同时帮助维持该地区的高质量生活和经济状况。 挑战 由于桌面系统&…

【Ceph的部署】

目录 一、基于 ceph-deploy 部署 Ceph 集群1、Ceph 生产环境推荐&#xff1a;2、Ceph 环境规划3、环境准备1、关闭 selinux 与防火墙2、根据规划设置主机名3、配置 hosts 解析4、安装常用软件和依赖包5、在 admin 管理节点配置 ssh 免密登录所有节点6、为每一个服务器配置时间同…

写给后端开发的『vue3』请求后端接口

本文分享一下在vue3前端项目中请求后端接口获取数据。比较简单&#xff0c;内容如下&#xff1a; 1、使用axios请求后端接口 首先npm install axios&#xff0c;添加axios依赖&#xff0c;就靠它来请求后端接口了&#xff0c;基本等同于使用jquery发ajax。 # src/main.js i…

每天一点Python——day58

#第五十八天 集合间的关系&#xff1a; 类似于数学中学到的集合一样&#xff0c;关系差不多&#xff0c;譬如相等&#xff0c;子集&#xff0c;交集 如图所示&#xff1a;#①两个集合是否相等&#xff1a;运用运算符【等号】或者运算符&#xff01;【不等号】进行判断 #例&…

【面试问题11】

1.Filter 和interceptor区别 filter是tomcat的规范,在请求前对request对象进行拦截,执行相关的过滤dofilter,例如url拦截请求静态文件,添加请求参数,权限检查,敏感字符检查等,请求后会再执行一次dofilter。区别,1. filter只tomcat规范,interceptor是spring规范。2.执行…

MySQL备份/恢复、索引、视图简述与练习

文章目录 MYSQL备份&#xff1a;物理备份&#xff1a;逻辑备份&#xff1a; 索引&#xff1a;原理&#xff1a;优缺点&#xff1a; 视图&#xff1a;什么是视图&#xff1a;作用&#xff1a;优点&#xff1a; 备份与恢复练习题&#xff1a;创库,建表&#xff1a;插入数据&#…

python系列教程210——嵌套lambda

朋友们&#xff0c;如需转载请标明出处&#xff1a;https://blog.csdn.net/jiangjunshow 声明&#xff1a;在人工智能技术教学期间&#xff0c;不少学生向我提一些python相关的问题&#xff0c;所以为了让同学们掌握更多扩展知识更好地理解AI技术&#xff0c;我让助理负责分享…

人工智能大语言模型微调技术:SFT 监督微调、LoRA 微调方法、P-tuning v2 微调方法、Freeze 监督微调方法

人工智能大语言模型微调技术&#xff1a;SFT 监督微调、LoRA 微调方法、P-tuning v2 微调方法、Freeze 监督微调方法 1.SFT 监督微调 1.1 SFT 监督微调基本概念 SFT&#xff08;Supervised Fine-Tuning&#xff09;监督微调是指在源数据集上预训练一个神经网络模型&#xff…

HTML5 Canvas API制作一个简单的猜字单机游戏

这篇文章主要介绍了借助HTML5 Canvas API制作一个简单的猜字单机游戏的实例分享,游戏中每局会自动生成一个字母,玩家按键盘来猜测该字母是哪一个,需要的朋友可以参考下 HTML代码 <!doctype html> <html lang"en"> <head> <met…

听GPT 讲K8s源代码--pkg(六)

pkg/kubelet/cm 目录是 Kubernetes 源代码中的一个目录&#xff0c;包含了 kubelet 组件中的 ConfigMap 相关代码。 在 Kubernetes 中&#xff0c;ConfigMap 是一种用于存储非机密数据的 API 对象类型&#xff0c;它可以用来存储配置信息、环境变量、命令行参数等等。 kubelet …

从脚手架搭建到部署访问路程梳理

1、vue-cli 起文件&#xff1a; 2、配置 webpack &#xff1a;打包配置等&#xff0c;env文件&#xff08; 处理线上和测试的ip&#xff09;&#xff0c; https://www.ibashu.cn/news/show_377892.html 3、样式&#xff1a;封装 style &#xff1a;组件&#xff08;element-u…

没有上司的舞会

题目 题目链接&#xff1a;285.没有上司的舞会 Ural 大学有 N 名职员&#xff0c;编号为 1∼N。 他们的关系就像一棵以校长为根的树&#xff0c;父节点就是子节点的直接上司。 每个职员有一个快乐指数&#xff0c;用整数 Hi 给出&#xff0c;其中 1≤i≤N。 现在要召开一场…

Effective Java笔记(16)要在公有类而非公有域中使用访问方法

有时候&#xff0c;可能需要编写一些退化类&#xff0c;它们没有什么作用&#xff0c;只是用来集中实例域 &#xff1a; class Point {public double x;public double y; }由于这种类的数据域是可以被直接访问的&#xff0c;这些类没有提供封装&#xff08; encapsulation &am…