C++开发基础之深入理解C++中的两种单例模式实现——线程安全与效率的权衡

news2025/7/10 22:42:23

引言:

单例模式是设计模式中的一种,它保证一个类仅有一个实例,并提供一个全局访问点。在C++中,实现单例模式的方式多种多样,但随着多线程应用的普及,如何确保单例模式在多线程环境下的线程安全性成为了一个重要问题。本文将探讨两种不同的单例模式实现方式:一种是使用std::call_oncestd::once_flag的C++11风格实现;另一种是利用互斥锁(mutex)的传统实现。

C++11风格的单例模式实现:

在C++11及更高版本中,可以利用std::call_oncestd::once_flag来保证单例模式的线程安全初始化,同时保持代码的简洁性。这种方式避免了显式管理互斥锁,从而减少了代码的复杂度和潜在的死锁风险。

Singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H

#include <mutex>

class Singleton {
public:
    static Singleton& getInstance();

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    const std::string& getGuid() const { return guid; }
private:
    Singleton();
    ~Singleton();

    static void initSingleton();

    static Singleton* instance;
    static std::once_flag initFlag;

    std::string guid; //添加一个私有字段;

};

#endif // SINGLETON_H

Singleton.cpp

#include "Singleton.h"
#include <objbase.h>
#include <sstream>
#include <iomanip>
#include <iostream>

// 静态成员变量定义
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;

static std::string generateGuid() {
    GUID guid;
    HRESULT hr = CoCreateGuid(&guid); // 生成新的GUID
    std::string guidStr;
    if (SUCCEEDED(hr))
    {
        // 将GUID转换为字符串
        std::stringstream ss;
        ss << std::hex << std::uppercase
            << std::setw(8) << std::setfill('0') << guid.Data1 << '-'
            << std::setw(4) << std::setfill('0') << guid.Data2 << '-'
            << std::setw(4) << std::setfill('0') << guid.Data3 << '-'
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[0] << '-'
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[1]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[2]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[3]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[4]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[5]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[6]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[7];

        guidStr = ss.str();
        std::cout << "Generated GUID: " << guidStr << std::endl;
    }
    else
    {
        std::cerr << "Failed to create GUID." << std::endl;
    }

    return guidStr;
}

Singleton::Singleton() 
{
    guid = generateGuid();
}

Singleton::~Singleton() {}

Singleton& Singleton::getInstance() {
    std::call_once(initFlag, &Singleton::initSingleton);
    return *instance;
}

void Singleton::initSingleton() {
    instance = new Singleton();
}

SingletonApp.cpp

// SingletonApp.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "Singleton.h"
#include <iostream>
#include <thread>

void checkSingleton() {
    Singleton& s = Singleton::getInstance();
    Sleep(1000);
    std::cout << "Thread ID: " << std::this_thread::get_id()
        << ", Singleton address: " << &s
        << ", guid: " << s.getGuid() << "\r\n";
}

int main()
{
    
    std::thread t1(checkSingleton);
    std::thread t2(checkSingleton);

    t1.join();
    t2.join();

    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();

    if (&s1 == &s2) {
        std::cout << "s1 and s2 point to the same instance." << std::endl;
    }
    else {
        std::cout << "s1 and s2 do not point to the same instance." << std::endl;
    }

    std::cout << "s1 guid: " << s1.getGuid() << std::endl;
    std::cout << "s2 guid: " << s2.getGuid() << std::endl;

    return 0;
}


执行结果

在这里插入图片描述

传统互斥锁(mutex)的单例模式实现:

在C++11之前,为了实现线程安全的单例模式,开发人员需要手动管理互斥锁,以确保在多线程环境中不会多次创建实例。这种方法虽然有效,但增加了代码的复杂性和维护难度。

Singleton2.h

#ifndef SINGLETON2_H
#define SINGLETON2_H

#include <windows.h> 
#include <string>

class CriticalSection {
public:
    CriticalSection() { InitializeCriticalSection(&m_mutex); }
    ~CriticalSection() { DeleteCriticalSection(&m_mutex); }

    CRITICAL_SECTION* Get() { return &m_mutex; }

private:
    CRITICAL_SECTION m_mutex;
};

class Singleton2
{
public:
    static Singleton2& getInstance();
    const std::string& getGuid() const { return guid; }
private:
    Singleton2(); 
    ~Singleton2();
    Singleton2(const Singleton2&) = delete; 
    Singleton2& operator=(const Singleton2&) = delete; 

    static Singleton2* instance;
    std::string guid; //添加一个私有字段;
};
#endif // SINGLETON2_H

Singleton2.cpp

#include "Singleton2.h"
#include <objbase.h>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <string>


// 静态成员变量定义
Singleton2* Singleton2::instance = nullptr;
CriticalSection mutex;

static std::string generateGuid() {
    GUID guid;
    HRESULT hr = CoCreateGuid(&guid); // 生成新的GUID
    std::string guidStr;
    if (SUCCEEDED(hr))
    {
        // 将GUID转换为字符串
        std::stringstream ss;
        ss << std::hex << std::uppercase
            << std::setw(8) << std::setfill('0') << guid.Data1 << '-'
            << std::setw(4) << std::setfill('0') << guid.Data2 << '-'
            << std::setw(4) << std::setfill('0') << guid.Data3 << '-'
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[0] << '-'
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[1]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[2]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[3]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[4]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[5]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[6]
            << std::setw(2) << std::setfill('0') << (unsigned int)guid.Data4[7];

        guidStr = ss.str();
        std::cout << "Generated GUID: " << guidStr << std::endl;
    }
    else
    {
        std::cerr << "Failed to create GUID." << std::endl;
    }

    return guidStr;
}

Singleton2::Singleton2() 
{ 
    guid = generateGuid();
}
Singleton2::~Singleton2() {}

Singleton2& Singleton2::getInstance()
{
    // 进入临界区,确保线程安全
    EnterCriticalSection(mutex.Get());

    // 如果还没有实例化,则创建实例
    if (instance == nullptr) {
        instance = new Singleton2();
    }
    // 离开临界区
    LeaveCriticalSection(mutex.Get());

    return *instance;
}

SingletonApp.cpp

// SingletonApp.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "Singleton2.h"
#include <iostream>
#include <thread>

void checkSingleton2() {
    Singleton2& s = Singleton2::getInstance();
    Sleep(1000);
    std::cout << "Thread ID: " << std::this_thread::get_id()
        << ", Singleton address: " << &s
        << ", guid: " << s.getGuid() << "\r\n";
}

int main()
{
    std::thread t1(checkSingleton2);
    std::thread t2(checkSingleton2);

    t1.join();
    t2.join();

    Singleton2& s1 = Singleton2::getInstance();
    Singleton2& s2 = Singleton2::getInstance();

    if (&s1 == &s2) {
        std::cout << "s1 and s2 point to the same instance." << std::endl;
    }
    else {
        std::cout << "s1 and s2 do not point to the same instance." << std::endl;
    }

    std::cout << "s1 guid: " << s1.getGuid() << std::endl;
    std::cout << "s2 guid: " << s2.getGuid() << std::endl;

    return 0;
}

执行结果

在这里插入图片描述

总结:

两种实现方式各有优劣。C++11风格的实现更加现代、简洁且易于维护,它利用了语言特性来自动处理线程同步问题,避免了显式管理锁的麻烦。然而,在C++11之前的项目中,或者在某些对性能有极致要求的场景下,传统的互斥锁实现可能更为合适,因为它提供了更细粒度的控制和可能的性能优化空间。

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

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

相关文章

深度学习--图像分割UNet介绍及代码分析

UNet介绍 参考UNet网络介绍整体架构UNet过程输入编码器&#xff08;下采样&#xff09;中间特征表示解码器&#xff08;上采样&#xff09;输出 代码详解unetUP和Unet关系上采样模块——unetUp用于图像分割的卷积神经网络&#xff08;CNN&#xff09;架构模块——Unet类的定义初…

使用 Manim 创建一个二维坐标平面【NumberPlane】

NumberPlane 是 Manim 中用于创建一个二维坐标平面的类。它可以帮助用户在场景中可视化坐标轴、网格线以及其他数学概念。具体来说&#xff0c;它的功能包括&#xff1a; 坐标轴&#xff1a;NumberPlane 提供了 x 轴和 y 轴&#xff0c;通常是中心对称的&#xff0c;允许用户清…

深入探究Python反序列化漏洞:原理剖析与实战复现

在现代应用程序开发中&#xff0c;Python反序列化漏洞已成为一个备受关注的安全问题。反序列化是Python中用于将字节流转换回对象的过程&#xff0c;但如果没有妥善处理&#xff0c;攻击者可以通过精心构造的恶意数据&#xff0c;利用反序列化漏洞执行任意代码&#xff0c;进而…

前端day4-表单标签

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>day4-表单</title> </head> <body&g…

# 基于MongoDB实现商品管理系统(2)

基于MongoDB实现商品管理系统&#xff08;2&#xff09; 基于 mongodb 实现商品管理系统之准备工作 1、案例需求 这里使用的不是前端页面&#xff0c;而是控制台来完成的。 具体的需求如下所示&#xff1a; 运行 查询所有 通过id查询详情 添加 - 通过id删除 2、案例分析 程…

进程创建,进程消亡

虚拟地址&#xff1a;通过虚拟技术&#xff0c;将外部存储设备的一部分空间&#xff0c;划分给系统&#xff0c;作为在内存不足时临时用作数据缓存。当内存耗尽时&#xff0c;电脑就会自动调用硬盘来充当内存&#xff0c;以缓解内存的紧张。 练习: 编写一个代码实现,一个父…

OGG转MP3音频格式转换:6种免费音频转换器推荐

在如今的数字音乐时代&#xff0c;不同音频格式的兼容性问题常常让我们感到困扰。其中&#xff0c;OGG和MP3是两种常见的音频格式&#xff0c;但由于设备和平台的支持问题&#xff0c;我们经常需要将OGG转换为MP3格式。 本文将为您详细介绍OGG和MP3的区别&#xff0c;为什么需要…

Spring Boot集成protobuf快速入门Demo

1.什么是protobuf&#xff1f; Protobuf&#xff08;Protocol Buffers&#xff09;是由 Google 开发的一种轻量级、高效的数据交换格式&#xff0c;它被用于结构化数据的序列化、反序列化和传输。相比于 XML 和 JSON 等文本格式&#xff0c;Protobuf 具有更小的数据体积、更快…

数据结构:队列(含源码)

目录 一、队列的概念和结构 二、队列的实现 头文件 初始化 入队列和出队列 获取队头队尾元素 队列有效数据数及队列判空 队列的销毁 完整源码 dl.h dl.c 一、队列的概念和结构 队列是一种只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性…

重生之我 学习【数据结构之顺序表(SeqList)】

⭐⭐⭐ 新老博友们&#xff0c;感谢各位的阅读观看 期末考试&假期调整暂时的停更了两个多月 没有写博客为大家分享优质内容 还容各位博友多多的理解 美丽的八月重生之我归来 继续为大家分享内容 你我共同加油 一起努力 ⭐⭐⭐ 数据结构将以顺序表、链表、栈区、队列、二叉树…

多米诺和托米诺平铺

有两种形状的瓷砖&#xff1a;一种是2 x 1的多米诺形&#xff0c;另一种是形如L的托米诺形。两种形状都可以旋转。 给定整数 n &#xff0c;返回可以平铺 2 x n 的面板的方法的数量。返回对 10^9 7 取模 的值。 平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺不同&#xff…

maven常用命令与常见问题汇总

文章目录 一、IDEA 下载依赖包源码报错Sources not found for:xxxx二、常用命令1、打包 一、IDEA 下载依赖包源码报错Sources not found for:xxxx 解决方案&#xff1a; 方案1、在 terminal 运行 mvn dependency:resolve -Dclassifiersources 命令 方案2、右键特定的pom文件…

论文概览 |《IJGIS》2024 Vol.38 issue4

本次给大家整理的是《International Journal of Geographical Information Science》杂志2024年第38卷第4期的论文的题目和摘要&#xff0c;一共包括8篇SCI论文&#xff01; 论文1 knowledge-constrained large language model interactable with GIS: enhancing public risk …

笔试题 day1

目录 快速io 统计2的个数 两个数组的交集 点击消除 快速io import java.util.*; import java.io.*;public class Main {public static PrintWriter out new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));public static Read in new Read();publ…

瑞_Linux防火墙相关命令_Windows远程连接虚拟机的服务失败_Linux防火墙端口开放

&#x1f64a; 前言&#xff1a;博主在学习使用虚拟机的过程中&#xff0c;常常碰到 Windows 远程连接虚拟机的服务失败的问题。比如想要在主机上连接虚拟机中的 MongoDB 服务的时候&#xff0c;服务器或者虚拟机一般都会默认开启防火墙&#xff0c;则会导致远程连接失败&#…

做一个能和你互动玩耍的智能机器人之七-接入对话和大模型

接入科大迅飞的语音识别&#xff1a; private void printResult(RecognizerResult results) {String text JsonParser2.parseIatResult(results.getResultString());String sn null;// 读取json结果中的sn字段try {JSONObject resultJson new JSONObject(results.getResult…

如何忽略已经提交到 Git 仓库中的文件

文章目录 前言一、确认文件是否已经被提交二、确认 .git 文件存在三、修改 .git/info/exclude 文件四、修改文件名五、提交和推送六、验证总结 前言 在日常开发中&#xff0c;我们常常会遇到这样的情况&#xff1a;不小心将不应追踪的文件提交到了 Git 仓库中&#xff0c;例如…

LabVIEW中的Reverse String函数与字节序转换

在LabVIEW中&#xff0c;数据的字节序&#xff08;也称为端序&#xff09;问题通常出现在数据传输和存储过程中。字节序可以分为大端&#xff08;Big-Endian&#xff09;和小端&#xff08;Little-Endian&#xff09;&#xff0c;它们分别表示高字节存储在低地址和低字节存储在…

培训第二十二天(mysql数据库主从搭建)

上午 1、为mysql添加开机启动chkconfig [rootmysql1 ~]# chkconfig --list //列出系统服务在不同运行级别下的启动状态注&#xff1a;该输出结果只显示 SysV 服务&#xff0c;并不包含原生 systemd 服务。SysV 配置数据可能被原生 systemd 配置覆盖。 要列出 systemd 服务…

2024.8.2(MySQL)

一、mysql 1、下载mysql软件包 [rootmysql ~]# yum -y install wget [rootmysql ~]# wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 2、解压 [rootmysql ~]# tar -xf mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 3、安装…