当前位置:首页 > 技术 > 正文内容

C++多线程环境下的单例模式实现与比较

访客 技术 2026年6月12日 1

什么是线程安全?

多线程并行执行且存在共享数据的应用程序中,线程安全的实现通过同步机制确保各线程能够正确执行,避免数据竞争和状态不一致等问题。

线程安全保证方法

  1. 互斥锁机制:为共享资源添加锁,确保同一时间只有一个线程可以访问。
  2. 线程局部存储:为每个线程维护独立资源副本,避免共享,如C++中的thread_local关键字。

单例模式概述

单例模式是一种设计模式,它保证在整个应用程序生命周期中,某个类只有一个实例存在,并提供全局访问点。

单例模式分类

根据实例化时机的不同,单例模式可分为:

  • 延迟初始化:在首次使用时才创建实例,节省资源但需考虑线程安全问题。
  • 立即初始化:在程序启动时就创建实例,直接使用,天生线程安全。

单例模式核心特征

  • 私有构造与析构函数,防止外部直接创建
  • 私有拷贝构造与赋值操作符,防止复制
  • 公有静态获取实例方法,提供全局访问点

01 基础延迟初始化单例(非线程安全)

///////////////////  基础延迟初始化实现 -- 非线程安全 //////////////////
#include <iostream>
#include <mutex>
#include <pthread.h>

class UniqueInstance
{

public:
    // 获取单例对象
    static UniqueInstance *GetInstance();

    // 释放单例资源
    static void ReleaseInstance();
	
	// 显示实例地址
    void ShowAddress();

private:
	// 私有构造和析构函数
    UniqueInstance();
    ~UniqueInstance();

    // 私有拷贝和赋值操作
    UniqueInstance(const UniqueInstance &other);
    const UniqueInstance &operator=(const UniqueInstance &other);

private:
    // 单例指针
    static UniqueInstance *m_Instance;
};

// 初始化静态成员
UniqueInstance *UniqueInstance::m_Instance = nullptr;

UniqueInstance* UniqueInstance::GetInstance()
{
	if (m_Instance == nullptr)
	{
		m_Instance = new (std::nothrow) UniqueInstance; // 无锁实现,多线程环境下不安全
	}
    return m_Instance;
}

void UniqueInstance::ReleaseInstance()
{
    if (m_Instance)
    {
        delete m_Instance;
        m_Instance = nullptr;
    }
}

void UniqueInstance::ShowAddress()
{
	std::cout << "实例内存地址: " << this << std::endl;
}

UniqueInstance::UniqueInstance()
{
    std::cout << "构造函数执行" << std::endl;
}

UniqueInstance::~UniqueInstance()
{
    std::cout << "析构函数执行" << std::endl;
}

///////////////////  基础延迟初始化实现 -- 非线程安全 //////////////////

// 线程函数
void *ThreadFunction(void *threadid)
{
    // 线程分离
    pthread_detach(pthread_self());

    // 获取线程ID
    int tid = *((int *)threadid);
    std::cout << "线程 ID: [" << tid << "] 开始执行" << std::endl;

    // 获取并显示实例
    UniqueInstance::GetInstance()->ShowAddress();

    pthread_exit(nullptr);
}

#define THREAD_COUNT 5

int main()
{
    pthread_t threads[THREAD_COUNT] = {0};
    int thread_ids[THREAD_COUNT] = {0};

    std::cout << "主线程: 开始创建线程..." << std::endl;

    for (int i = 0; i < THREAD_COUNT; i++)
    {
        std::cout << "主线程: 创建线程 [" << i << "]" << std::endl;
        thread_ids[i] = i;
        
        // 创建线程
        int result = pthread_create(&threads[i], nullptr, ThreadFunction, (void *)&thread_ids[i]);
        if (result)
        {
            std::cout << "错误: 线程创建失败, 错误码: " << result << std::endl;
            return -1;
        }
    }

    // 释放单例资源
    UniqueInstance::ReleaseInstance();
    std::cout << "主线程: 程序结束!" << std::endl;
	
    return 0;
}

基础延迟初始化单例测试结果:

测试显示构造函数被调用了两次,产生了两个不同的实例地址(如0x7f3c980008c00x7f3c900008c0),证明在多线程环境下这种实现是不安全的。

02 互斥锁保护的延迟初始化单例(线程安全)

///////////////////  互斥锁保护的延迟初始化实现  //////////////////
class SecureSingleton
{

public:
    // 获取单例对象
    static SecureSingleton *&GetInstance();

    // 释放单例资源
    static void ReleaseInstance();
	
    // 显示实例地址
    void ShowAddress();

private:
    // 私有构造和析构函数
    SecureSingleton();
    ~SecureSingleton();

    // 私有拷贝和赋值操作
    SecureSingleton(const SecureSingleton &other);
    const SecureSingleton &operator=(const SecureSingleton &other);

private:
    // 单例指针
    static SecureSingleton *m_Instance;
    static std::mutex m_Mutex;
};

// 初始化静态成员
SecureSingleton *SecureSingleton::m_Instance = nullptr;
std::mutex SecureSingleton::m_Mutex;

SecureSingleton *&SecureSingleton::GetInstance()
{
    // 双重检查锁定模式
    if (m_Instance == nullptr) 
    {
        std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
        if (m_Instance == nullptr)
        {
            m_Instance = new (std::nothrow) SecureSingleton;
        }
    }
    return m_Instance;
}

void SecureSingleton::ReleaseInstance()
{
    std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
    if (m_Instance)
    {
        delete m_Instance;
        m_Instance = nullptr;
    }
}

void SecureSingleton::ShowAddress()
{
	std::cout << "实例内存地址: " << this << std::endl;
}

SecureSingleton::SecureSingleton()
{
    std::cout << "构造函数执行" << std::endl;
}

SecureSingleton::~SecureSingleton()
{
    std::cout << "析构函数执行" << std::endl;
}
///////////////////  互斥锁保护的延迟初始化实现  //////////////////

互斥锁保护的延迟初始化单例测试结果:

测试显示只创建了一个实例,地址为0x7f28b00008c0,证明互斥锁保护下的延迟初始化是线程安全的。

03 静态局部变量的延迟初始化单例(C++11线程安全)

///////////////////  静态局部变量的延迟初始化实现  //////////////////
class LazySingleton
{

public:
    // 获取单例对象
    static LazySingleton &GetInstance();
	
	// 显示实例地址
    void ShowAddress();

private:
    // 私有构造函数
    LazySingleton();

    // 私有析构函数
    ~LazySingleton();

    // 私有拷贝构造
    LazySingleton(const LazySingleton &other);

    // 私有赋值操作
    const LazySingleton &operator=(const LazySingleton &other);
};

LazySingleton &LazySingleton::GetInstance()
{
    // 利用局部静态变量特性实现单例
    static LazySingleton instance;
    return instance;
}

void LazySingleton::ShowAddress()
{
	std::cout << "实例内存地址: " << this << std::endl;
}

LazySingleton::LazySingleton()
{
    std::cout << "构造函数执行" << std::endl;
}

LazySingleton::~LazySingleton()
{
    std::cout << "析构函数执行" << std::endl;
}
///////////////////  静态局部变量的延迟初始化实现  //////////////////

静态局部变量的延迟初始化单例测试结果:

使用C++11标准(-std=c++11)编译时,静态局部变量的实现是线程安全的,只创建一次实例(地址如0x6016e8)。这种方法代码简洁,推荐使用。

04 立即初始化单例(天生线程安全)

////////////////////////// 立即初始化实现 /////////////////////
class EagerSingleton
{
public:
    // 获取单例
    static EagerSingleton* GetInstance();

    // 释放单例资源
    static void ReleaseInstance();
    
    // 显示实例地址
    void ShowAddress();

private:
    // 私有构造和析构函数
    EagerSingleton();
    ~EagerSingleton();

    // 私有拷贝和赋值操作
    EagerSingleton(const EagerSingleton &other);
    const EagerSingleton &operator=(const EagerSingleton &other);

private:
    // 单例指针,程序启动时初始化
    static EagerSingleton *m_Instance;
};

// 程序启动时即创建实例,天生线程安全
EagerSingleton* EagerSingleton::m_Instance = new (std::nothrow) EagerSingleton;

EagerSingleton* EagerSingleton::GetInstance()
{
    return m_Instance;
}

void EagerSingleton::ReleaseInstance()
{
    if (m_Instance)
    {
        delete m_Instance;
        m_Instance = nullptr;
    }
}

void EagerSingleton::ShowAddress()
{
    std::cout << "实例内存地址: " << this << std::endl;
}

EagerSingleton::EagerSingleton()
{
    std::cout << "构造函数执行" << std::endl;
}

EagerSingleton::~EagerSingleton()
{
    std::cout << "析构函数执行" << std::endl;
}
////////////////////////// 立即初始化实现 /////////////////////

立即初始化单例测试结果:

立即初始化方式在程序启动时即创建实例,因此天生就是线程安全的。

实现方案比较与选择

  • 延迟初始化:以时间换空间,适用于资源访问较少的场景。推荐使用静态局部变量的C++11实现,代码简洁且线程安全。
  • 立即初始化:以空间换时间,适用于资源访问频繁或高并发环境,启动时创建实例确保线程安全。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。