C++设计模式1:单例模式(懒汉模式和饿汉模式,以及多线程问题处理)

2024-08-22 23:44

本文主要是介绍C++设计模式1:单例模式(懒汉模式和饿汉模式,以及多线程问题处理),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

饿汉单例模式

        程序还没有主动获取实例对象,该对象就产生了,也就是程序刚开始运行,这个对象就已经初始化了。 

class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){return &singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton singleton;
};
Singleton Singleton::singleton;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

        显然饿汉模式是线程安全的,因为单例对象的初始化发生在.bss段,和栈无关,而线程的启动依赖于函数,函数需要开辟栈内存,所以是线程安全的。但是饿汉模式也有缺点,如果这个单例类的构造函数过于复杂,包含了线程和数据库等等一系列的初始化过程,需要进行大量操作,就会导致程序启动变慢。

运行结果如下:    三个对象的地址是一样的,说明是同一个对象,并且最后也只是析构了一次。

 懒汉模式

实例对象直到程序中有模块获取它时,才会初始化这个对象。

#include<iostream>
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){if (singleton == nullptr){singleton = new Singleton();}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

运行结果。 

         上面这种写法显然是线程不安全的,因为要构造一个单例,构造函数里面可能需要进行大量的操作。这段代码就会产生竞态条件,我们需要通过线程间的互斥操作来解决。

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){std::lock_guard<std::mutex>loc(mtx);if (singleton == nullptr){singleton = new Singleton();}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

         这种写法虽然可以解决问题,但是加锁的位置,对程序的性能损耗较大,每次要先拿到锁才去判断是否为nullptr,如果不是,这把锁就白拿了,换一下加锁的位置。

        

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){if (singleton == nullptr){std::lock_guard<std::mutex>loc(mtx);singleton = new Singleton();}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

        这次加锁位置明显可以减少程序的性能损耗,但是会出现一个问题,假如开始单例是nullptr,一个线程通过if语句,并且拿到了锁,它只是开辟了内存,并且构造了单例对象,但是构造过程没有执行完全,还没有给这个单例对象赋值, 这时候这个单例还是nullptr,另一个线程这时候也可以通过if语句了,因为单例是nullptr,但是它不能构造单例,因为没有拿到锁,这时候第一个线程给单例赋值完成后,释放了锁,第二个线程拿到锁,就又构造了一次单例。

        要解决这个问题也简单,那就是双重if语句判断。

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){if (singleton == nullptr){std::lock_guard<std::mutex>loc(mtx);if (singleton == nullptr){singleton = new Singleton();}}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

运行结果还是一样的。

        如果我们要简化上面的写法呢?我们可以使用到函数静态局部变量的初始化机制,函数静态局部变量在初始化的时候,底层的汇编指令会自动添加上线程互斥的指令,就可以省去我们加锁的步骤了。而且只有当程序主动调用get_instance函数的时候,单例才会被初始化,也省去了我们的nullptr双重判断了。

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){static Singleton singleton;return &singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

运行效果一样。 

这篇关于C++设计模式1:单例模式(懒汉模式和饿汉模式,以及多线程问题处理)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Spring的RedisTemplate的json反序列泛型丢失问题解决

《Spring的RedisTemplate的json反序列泛型丢失问题解决》本文主要介绍了SpringRedisTemplate中使用JSON序列化时泛型信息丢失的问题及其提出三种解决方案,可以根据性... 目录背景解决方案方案一方案二方案三总结背景在使用RedisTemplate操作redis时我们针对

Java堆转储文件之1.6G大文件处理完整指南

《Java堆转储文件之1.6G大文件处理完整指南》堆转储文件是优化、分析内存消耗的重要工具,:本文主要介绍Java堆转储文件之1.6G大文件处理的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言文件为什么这么大?如何处理这个文件?分析文件内容(推荐)删除文件(如果不需要)查看错误来源如何避

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

Kotlin Map映射转换问题小结

《KotlinMap映射转换问题小结》文章介绍了Kotlin集合转换的多种方法,包括map(一对一转换)、mapIndexed(带索引)、mapNotNull(过滤null)、mapKeys/map... 目录Kotlin 集合转换:map、mapIndexed、mapNotNull、mapKeys、map

nginx中端口无权限的问题解决

《nginx中端口无权限的问题解决》当Nginx日志报错bind()to80failed(13:Permissiondenied)时,这通常是由于权限不足导致Nginx无法绑定到80端口,下面就来... 目录一、问题原因分析二、解决方案1. 以 root 权限运行 Nginx(不推荐)2. 为 Nginx

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原

Java docx4j高效处理Word文档的实战指南

《Javadocx4j高效处理Word文档的实战指南》对于需要在Java应用程序中生成、修改或处理Word文档的开发者来说,docx4j是一个强大而专业的选择,下面我们就来看看docx4j的具体使用... 目录引言一、环境准备与基础配置1.1 Maven依赖配置1.2 初始化测试类二、增强版文档操作示例2.

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域