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

引言:

单例模式是设计模式中的一种,它保证一个类仅有一个实例,并提供一个全局访问点。在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之前的项目中,或者在某些对性能有极致要求的场景下,传统的互斥锁实现可能更为合适,因为它提供了更细粒度的控制和可能的性能优化空间。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dotnet研习社

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值