设计模式——C++11实现单例模式(饿汉模式、懒汉模式),与单例的进程

本文将介绍单例模式,使用C++11实现多个版本的单例模式,分析各自的优缺点。最后提及如何实现一个单例的进程。

什么是单例模式

单例模式属于创建型模式,提供了一种创建对象的方式。
单例模式确保一个类只有一个实例。通过一个类统一地访问这个实例。
思想:将构造函数设置为私有,通过一个接口获取类对象。如果对象则创建,否则直接返回。

最简单的单例模式——线程不安全

class Singleton_1
{
public:static Singleton_1* GetInstance(){if(m_instance == nullptr){m_instance = new Singleton_1();}return m_instance;}private:Singleton_1() = default;Singleton_1(const Singleton_1&) = delete;Singleton_1(Singleton_1&&) = delete;	/* 不需要 */Singleton_1& operator=(const Singleton_1&) = delete;Singleton_1& operator=(Singleton_1&&) = delete;	/* 不需要 */
private:static Singleton_1* m_instance;
};
Singleton_1* Singleton_1::m_instance = nullptr;

首先把构造函数全部私有,无参构造选择默认生成,拷贝构造和赋值运算符重载选择删除。不需要显式把移动构造和移动赋值删除,因为右值会默认匹配到const左值引用。
GetInstance:判断类内静态成员instance是否为空,空则new一个对象,否则直接返回。
这样的程序在单执行流下是没问题的,但是如果是多线程,可能导致:

  • 线程A进入了GetInstance,判断instance为空,则准备new对象。在执行new之前由于系统调度,线程被切走,记录了上下文,此时属于就绪态,在就绪队列中排队。此时instance仍为空。
  • 线程B进入了GetInstance,判断instance也是为空,准备new对象。此时instance为空,但是有两个线程进入了作用域,就出现了问题。
  • 最终会new出来两个对象,一个丢失在内存中,导致内存泄露,一个后续会被真正使用。

由于m_instance在多线程情况下是临界资源,所以可以考虑加锁。

线程安全的单例模式——多次锁

class Singleton_2
{
public:static Singleton_2* GetInstance(){std::unique_lock<std::mutex> lock(m_mutex);if(m_instance == nullptr){m_instance = new Singleton_2();}return m_instance;}private:Singleton_2() = default;Singleton_2(const Singleton_2&) = delete;Singleton_2& operator=(const Singleton_2&) = delete;private:static Singleton_2* m_instance;static std::mutex m_mutex;
};
Singleton_2* Singleton_2::m_instance = nullptr;

在进入GetInstance的时候就加锁,因为要访问instance这个临界资源,才能保证每次只有一个线程在读或写这个资源。同时注意unique_lock构造时传入的mutex也应该是static的。因为static函数中没有this指针,编译器找不到这个mutex,因此需要声明一个静态的mutex。

但是问题在于,每次调用GetInstance的时候,无论对象是否已经实例化,都需要加锁再解锁。这样是有问题的,我们期望在对象没创建之前就要加锁,创建之后已经没必要了,因为每次只去获取对象。
基于上面的缺点,引入了一种解决方式,双重检测。

线程安全的单例模式——单次锁

class Singleton_3
{
public:static Singleton_3* GetInstance(){if(m_instance == nullptr){std::unique_lock<std::mutex> lock(m_mutex);if(m_instance == nullptr)m_instance = new Singleton_3();}return m_instance;}private:Singleton_3() = default;Singleton_3(const Singleton_3&) = delete;Singleton_3& operator=(const Singleton_3&) = delete;private:static Singleton_3* m_instance;static std::mutex m_mutex;
};

对m_instance做两次检查。

  • 第一次:如果已经实例化,直接返回。否则尝试获取锁。
  • 第二次:获取锁后,再次判断是否已经实例化,如果是则返回,否则再实例化对象。
    这样的好处是,就算多个线程判断到了m_instance为空,进入了if,也能保证线程安全地实例化对象。并且实例化之后,后续获取对象都不需要用到锁,相比第二种方式提高了运行的效率。

Meyers单例模式——C++11

class Singleton
{
public:static Singleton& GetInstance(){static Singleton instance;return instance;}private:Singleton() = default;Singleton(const Singleton& other) = delete;Singleton& operator=(const Singleton&) = delete;
};

Meyers单例是利用了C++11的特性而实现的单例模式,大道至简。主要依赖于C++11及以后,静态局部变量的初始化是线程安全的,能够保证安全和效率性。

饿汉模式(Eager Initialization)

饿汉模式是指:一个对象在程序正式被执行之前就实例化。
C/C++中,静态变量或者全局变量是存储在静态区。局部静态变量在运行时分配,全局变量、类内静态成员在编译时分配。因此可以定义m_instance为静态成员变量,并且初始化的时候分配内存。

class Singleton_Eager
{
public:static Singleton_Eager* GetInstance(){return m_instance;}
private:Singleton_Eager() = default;Singleton_Eager(const Singleton_Eager& other) = delete;Singleton_Eager& operator=(const Singleton_Eager&) = delete;private:static Singleton_Eager* m_instance;
};
Singleton_Eager* Singleton_Eager::m_instance = new Singleton_Eager();

这样的写法使得每次调用GetInstance获取对象都是线程安全的。

懒汉模式(Lazy Initialization)

懒汉模式是指:在第一次要访问对象的时候再实例化。
本篇文章除了上述的饿汉模式,其他都是懒汉模式。

饿汉和懒汉的区别

饿汉模式
优点:运行时速度快,不存在线程安全的问题。
缺点:程序运行前会分配内存,如果对象比较大,或者要求进程启动时间短的场景可能不适用。

懒汉模式
优点:=-=
缺点:需要控制线程互斥,运行时再加载。

如何实现单例的进程?

在某些场景,比如网络服务程序、嵌入式系统的应用如车机仪表和中控,需要控制同一时间只能有一个程序加载到内存中并运行。
Linux提供了文件锁的接口,可以达到这个目的。

/* Operations for the `flock' call.  */
#define	LOCK_SH	1	/* Shared lock.  */
#define	LOCK_EX	2 	/* Exclusive lock.  */
#define	LOCK_UN	8	/* Unlock.  *//* Can be OR'd in to one of the above.  */
#define	LOCK_NB	4	/* Don't block when locking.  *//* Apply or remove an advisory lock, according to OPERATION,on the file FD refers to.  */
extern int flock (int __fd, int __operation) __THROW;Apply or remove an advisory lock on the open file specified by fd.  The argument operation is one of the following:
应用或者去除一个建议性质的锁LOCK_SH  Place a shared lock.  More than one process may hold a shared lock for a given file at a given time.LOCK_EX  Place an exclusive lock.  Only one process may hold an exclusive lock for a given file at a given time.LOCK_UN  Remove an existing lock held by this process.A call to flock() may block if an incompatible lock is held by another process.  To make a nonblocking request, include LOCK_NB (by ORing) with any of the aboveoperations.A single file may not simultaneously have both shared and exclusive locks.Locks created by flock() are associated with an open file description (see open(2)).  This means that duplicate  file  descriptors  (created  by,  for  example,fork(2) or dup(2)) refer to the same lock, and this lock may be modified or released using any of these file descriptors.  Furthermore, the lock is released ei‐ther by an explicit LOCK_UN operation on any of these duplicate file descriptors, or when all such file descriptors have been closed.If a process uses open(2) (or similar) to obtain more than one file descriptor for the same file, these file descriptors are treated independently  by  flock().An attempt to lock the file using one of these file descriptors may be denied by a lock that the calling process has already placed via another file descriptor.A  process  may hold only one type of lock (shared or exclusive) on a file.  Subsequent flock() calls on an already locked file will convert an existing lock tothe new lock mode.Locks created by flock() are preserved across an execve(2).A shared or exclusive lock can be placed on a file regardless of the mode in which the file was opened.RETURN VALUEOn success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
#include <unistd.h>
#include <sys/file.h>int main()
{/* 进程单例Linux为解决多进程读写同一文件时的冲突,引入了flock但是这个锁不是物理上的锁,而是建议性质的其他进程可以忽略该锁而直接进行读写*/std::string str = "Hello";printf("main : %s\n", str);umask(0);signal(2, handler);std::string fileName = ".lock";int fd = open(fileName.c_str(), O_CREAT | O_RDONLY, 0666);if(fd < 0){printf("fd = %d\n", fd);exit(-1);}/* 在打开一个文件后,尝试对其加锁,如果加锁失败,则直接退出 */int ret = flock(fd, LOCK_EX | LOCK_NB);if(ret < 0){printf("ret = %d, what = %s\n",fd, strerror(errno));exit(-1);}while(true){}return 0;
}

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

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

相关文章

Redis带你深入学习数据类型set

目录 1、set 2、set相关命令 2.1、添加元素 sadd 2.2、获取元素 smembers 2.3、判断元素是否存在 sismember 2.4、获取set中元素数量 scard 2.5、删除元素spop、srem 2.6、移动元素smove 2.7、集合中相关命令&#xff1a;sinter、sinterstore、sunion、sunionstore、s…

CSS:屏幕正中间有个元素A,元素A中有文字A,随着屏幕宽度的增加

始终需要满足以下条件&#xff1a; A元素垂直居中于屏幕***&#xff1b;A元素距离屏幕左右边距各10px&#xff1b;A元素里面的文字”A”的font-size:20px&#xff1b;水平垂直居中;A元素的高度始终是A元素宽度的50%; (如果搞不定可以实现为A元素的高度固定为200px;)请用 html及…

编程大师-分布式

分布式锁 mysql redis 【IT老齐122】不只setnx&#xff0c;两张图说清Redisson的Redis分布式锁实现_哔哩哔哩_bilibili zk 用这种方式去实现&#xff0c;zookeeper分布式锁&#xff0c;你会吗&#xff1f;_哔哩哔哩_bilibili

【Unity基础】3.脚本控制物体运动天空盒

【Unity基础】3.脚本控制物体运动&天空盒 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;搭建开发环境 &#xff08;1&#xff09;下载visual studio 在我们下载unity编译器的时候&…

C语言对单链表所有操作与一些相关面试题

目录 单链表的特性 单链表的所有操作 定义一个单链表 创建一个链表头 插入数据(头插法) 插入数据(尾插法) 查找节点 修改数据节点 删除节点 打印数据 销毁链表 翻转链表 打印链表长度 冒泡排序 快排 堆排 查找倒数第K个节点&#xff08;双指针法&#xff09; …

unix多进程多线程

概要 线程和进程是两种不同的并发执行单元&#xff0c;它们都可以实现多任务的执行&#xff0c;但是有各自的特点和区别。 线程是操作系统独立调度和执行的基本单位&#xff0c;它是进程的一个组成部分&#xff0c;一个进程可以包含多个线程&#xff0c;它们共享进程的地址空…

Microsoft Edge网页视频播放绿屏解决方法(B站)

一&#xff1a;问题&#xff0c;在B站观看视频时有绿色条纹 二&#xff1a;查找原因&#xff0c;未知 三&#xff1a;解决方法 三.1网页设置关闭硬件加速 三.2 点击视频播放下的 “小齿轮”&#xff0c;然后点击“更多播放设置” 把播放策略 “默认” 改为“AVC” 四&…

Tomcat配置域名和端口

Tomcat配置域名和端口 1.进入tomcat文件夹2. cd 到你的tomcat下3. 修改server.xml文件中监听端口4. 重启tomcat 1.进入tomcat文件夹 2. cd 到你的tomcat下 3. 修改server.xml文件中监听端口 继续修改server.xml中Host 4. 重启tomcat 进入bin ./shutdown.sh ./startup.sh …

etcd分布式存储

etcd分布式存储 etcd简介etcd下载安装etcd常用命令etcd配置参数etcd集群golang操作etcd

C++ map clear内存泄漏问题

map值存的是指针 map自带的clear()函数会清空map里存储的所有内容&#xff0c;但如果map值存储的是指针&#xff0c;则里面的值不会被清空&#xff0c;会造成内存泄漏&#xff0c;所以值为指针的map必须用迭代器清空。 使用erase迭代删除 迭代器删除值为指针的map&#xff0c…

rrweb入门

rrweb 背景 rrweb 是 record and replay the web&#xff0c;是当下很流行的一个录制屏幕的开源库。与我们传统认知的录屏方式&#xff08;如 WebRTC&#xff09;不同的是&#xff0c;rrweb 录制的不是真正的视频流&#xff0c;而是一个记录页面 DOM 变化的 JSON 数组&#x…

vue elementui 组合式 api 对于容器的滑动条的位置的获取与设置。切换页面可以保持原来的容器里的滑动条位置不变

需要使用 addEventListener 的方法获取滑动条的位置 xxx.vue 页面是一直缓存的&#xff0c;所以使用路由进入钩子&#xff08;onActivated&#xff09;设置滑动条的位置 App.vue: ...<el-container><router-view v-slot"{ Component }"><keep-alive…

Python基础篇(07):高阶函数lambda、zip、map、filter、reduce和函数注解

一、匿名函数 lambda 表达式 1、格式 lambda 参数: 表达式 冒号前是参数&#xff0c;可以有多个&#xff0c;用逗号隔开冒号右边的为表达式返回值是一个函数对象 2、举例&#xff1a;一个最简单的lambda函数 add lambda x, y: x y print(type(add)) # <class func…

Ubuntu 22.04 安装 Docker Engine

Install Docker Engine on Ubuntu | Docker Docs 比较方便的安装方式为通过 Apt Repo 来安装&#xff0c;需要三大步&#xff1a; 1. 预备仓库信息&#xff1a; 逐行执行一下命令 # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificat…

R语言---使用runway进行机器学习模型性能的比较

R语言—使用runway进行机器学习模型性能的比较 #dataloadrm(list=ls())#librarylibrary(dcurves)library(gtsummary)library(tidyverse)library(mlr3verse)library(tidyverse)library(data.table)</

【鲁棒电力系统状态估计】基于投影统计的电力系统状态估计的鲁棒GM估计器(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SpringCloud Alibaba 入门到精通 - Nacos

SpringCloud Alibaba 常用组件 一、基础结构搭建1.父工程创建2.子工程创建 二、Nacos&#xff1a;注册中心1.服务端搭建2.注册中心-客户端搭建3.注册中心-管理页面4.注册中心-常用配置5.注册中心-核心功能总结 三、Nacos注册中心集成Load Balancer 、OpenFeign1.Nacos客户端集成…

一键部署k8s集群

前置动作 关闭防火墙 systemctl disable firewalld && systemctl stop firewalld 关闭SELinux sed -i s#SELINUXenforcing#SELINUXdisabled#g /etc/selinux/config && grep SELINUXdisabled /etc/selinux/config setenforce 0 getenforce 关闭swap # 关闭…

前端面试题JS篇(4)

浏览器缓存 浏览器缓存分为强缓存和协商缓存&#xff0c;当客户端请求某个资源时&#xff0c;获取缓存的流程如下&#xff1a; 先根据这个资源的一些 http header 判断它是否命中强缓存&#xff0c;如果命中&#xff0c;则直接从本地获取缓存资源&#xff0c;不会发请求到服务…

c语言练习44:深入理解strstr

深入理解strstr strstr作用展示&#xff1a; #include <stdio.h> #include <string.h> int main() {char str[] "This is a simple string";char* pch;pch strstr(str, "simple");/*strncpy(pch, "sample", 6);*/printf("%s…