引言:
单例模式是设计模式中的一种,它保证一个类仅有一个实例,并提供一个全局访问点。在C++中,实现单例模式的方式多种多样,但随着多线程应用的普及,如何确保单例模式在多线程环境下的线程安全性成为了一个重要问题。本文将探讨两种不同的单例模式实现方式:一种是使用std::call_once
和std::once_flag
的C++11风格实现;另一种是利用互斥锁(mutex)的传统实现。
C++11风格的单例模式实现:
在C++11及更高版本中,可以利用std::call_once
和std::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之前的项目中,或者在某些对性能有极致要求的场景下,传统的互斥锁实现可能更为合适,因为它提供了更细粒度的控制和可能的性能优化空间。