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

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

时间:2024-08-22 21:51:54浏览次数:11  
标签:std Singleton 多线程 单例 get singleton 模式 饿汉 include

饿汉单例模式

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

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;
}

运行效果一样。 

标签:std,Singleton,多线程,单例,get,singleton,模式,饿汉,include
From: https://blog.csdn.net/weixin_74027669/article/details/141438485

相关文章

  • 火影忍者2——漩涡鸣人(仙人模式)篇
    老规矩,谈火影~火影忍者之——漩涡鸣人(仙人模式)篇众所周知,鸣仙是一个早期的A忍,技能破坏力贼大,一般遇到鸣仙(除非我用了青水+神卡)我是直接退的普攻一技能螺旋丸普通状态下一技能长摁放出两个分身释放螺旋丸,属于远程技能,但释放时的施法动作容易被打断,点击是本体出去,......
  • Java设计模式之代理模式:静态代理VS动态代理,与其他模式的对比分析和案例解析
    一、代理模式简介代理模式(ProxyPattern)是一种结构型设计模式,它提供了一个代理对象,用来控制对另一个对象的访问。这种模式通常用于在访问对象时引入额外的功能,而不改变对象的接口。代理模式的核心思想是为其他对象提供一种代理,以控制对这个对象的访问。在现实生活中,代理模......
  • 设计模式之责任链模式
    责任链模式是面向对象的23种设计模式中的一种,属于行为模式范围。责任链模式(ChainofResponsibility),见名知意:就是每一个处理请求的处理器组合成一个链表,链表中的每个节点(执行器)都有机会处理发送的请求。大致的结构是这个样子: 举一个简单的例子:某公司有一名新员工要入职,则入职......
  • Flannel Wireguard 模式
    FlannelWireGuard模式一、环境信息主机IPubuntu172.16.94.141软件版本docker26.1.4helmv3.15.0-rc.2kind0.18.0clab0.54.2kubernetes1.23.4ubuntuosUbuntu20.04.6LTSkernel5.11.5内核升级文档二、安装服务kind配置......
  • Flannel IPsec 模式
    FlannelIPSec模式一、环境信息主机IPubuntu172.16.94.141软件版本docker26.1.4helmv3.15.0-rc.2kind0.18.0clab0.54.2kubernetes1.23.4ubuntuosUbuntu20.04.6LTSkernel5.11.5内核升级文档二、安装服务kind配置文件......
  • 分布式事务的Seata AT模式原理
    Seata官网地址:https://seata.apache.org/zh-cn/AT模式优点:无侵入式代码,只需要添加注解,底层采用Seata代理的数据源DataSourceProxy缺点:依赖于数据库,目前只适用于postgresql、oracle、mysql、polardb-x、sqlserver、达梦数据库等数据库,比如业务逻辑中含有redis、es等操作需要控......
  • 设计模式简介及PHP的35种设计模式(上)
    什么是模式??        有经验的00开发者(以及其他的软件开发者)建立了既有通用原则又有惯用方案的指令系统来指导他们编制软件。如果以结构化形式对这些问题、解决方案和命名进行描述使其系统化,那么这些原则和习惯用法就可以称为模式。例如,下面是一个模式样例:    ......
  • 多线程和多线程同步
    多线程和多线程同步多线程编程是现代软件开发中的一项关键技术,在多线程编程中,开发者可以将复杂的任务分解为多个独立的线程,使其并行执行,从而充分利用多核处理器的优势。然而,多线程编程也带来了挑战,例如线程同步、死锁和竞态条件等问题。本篇文章将深入探讨多线程编程的基本概念(......
  • 深度学习设计模式之策略模式
    文章目录前言一、介绍二、特点三、详细介绍1.核心组成2.代码示例3.优缺点优点缺点4.使用场景总结前言策略模式定义一系列算法,封装每个算法,并使它们可以互换。一、介绍策略模式(StrategyPattern)是一种行为型设计模式,它定义了一系列算法,并将每一个算法封装起来,使......
  • Python——常用行为模式
    行为模式(BehavioralDesignPatterns)主要解决的是对象之间的职责划分与协作问题。这类模式通过定义对象间的通信方式、责任分配和行为组织,帮助构建可扩展、灵活的系统,并且通过减少耦合和提高复用性来优化系统行为。常见的行为模式有:责任链模式(ChainofResponsibility)命......