目录
1、C++中构造函数并不是线程安全
-
不具备线程安全性:
- 当多个线程同时尝试创建同一个类的实例时,构造函数的执行过程并不会自动进行同步。这可能导致多个线程同时执行构造函数的代码,从而引发竞态条件或未定义行为。
-
构造函数的执行流程:
- 构造函数的执行流程包括了对象成员的初始化、基类构造函数的调用(如果有继承)、构造函数体内的逻辑执行等步骤。这些步骤在对象的创建过程中依次执行,但没有内置的机制来确保只有一个线程可以同时执行特定对象的构造函数。
2、多线程下的懒汉式单例模式
#include <iostream>
#include "Singleton.h"
int main()
{
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
// s1 和 s2 实际上是同一个对象
if (s1 == s2) {
std::cout << "Same instance" << std::endl;
}
return 0;
}
#ifndef SINGLETON_H
#define SINGLETON_H
#include <iostream>
#include <mutex>
using namespace std;
class Singleton
{
public:
Singleton();
static Singleton* getInstance();
private:
static Singleton* instance;
static std::mutex mtx; // 用于线程安全的互斥锁
};
#endif // SINGLETON_H
#include "Singleton.h"
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
Singleton::Singleton()
{
}
Singleton *Singleton::getInstance()
{
if(instance == nullptr)
{
// 加锁保证线程安全
std::lock_guard<std::mutex>lock(mtx);
if(instance == nullptr)
{
instance = new Singleton;
}
}
return instance;
}
解释说明
-
instance 和 mutex:
instance
是静态指针,用于存储单例对象的实例;mutex
是用于线程同步的互斥锁。 -
构造函数私有化:通过将构造函数声明为私有,可以防止外部代码直接实例化 Singleton 类,只能通过
getInstance()
方法获取单例实例。 -
getInstance() 方法:
- 使用
std::lock_guard<std::mutex>
来确保在多线程环境下对instance
的创建操作是原子的。 - 首先检查
instance
是否为nullptr
,如果是,则创建一个新的 Singleton 实例。 - 返回
instance
,确保在单例创建后,多个线程获取到的是同一个实例。
- 使用
特点和注意事项
-
延迟初始化:只有在第一次调用
getInstance()
方法时才会创建 Singleton 实例,节省了资源。 -
线程安全:通过互斥锁确保在多线程环境下只有一个线程可以创建实例,避免竞态条件。
-
性能考虑:虽然保证了线程安全,但每次调用
getInstance()
都需要获取互斥锁,可能会对性能产生一定影响,特别是在高并发场景下。 -
析构函数处理:如果需要在程序结束时销毁 Singleton 实例,需要在合适的地方加入对
instance
的删除操作,并确保线程安全。
3、std::lock_guard<std::mutex>
std::lock_guard<std::mutex>
是 C++ 标准库中提供的一种用于管理互斥锁的 RAII(资源获取即初始化)方式。它的作用是在创建时锁定互斥锁,并在作用域结束时自动解锁。
std::mutex mtx;
{
std::lock_guard<std::mutex> lock(mtx); // 锁定 mtx
// 在这个作用域内,mtx 已经被锁定
// 可以在这里安全地访问需要互斥访问的资源
} // lock 在这里的作用域结束,mtx 被自动解锁
// 在这里,mtx 不再被锁定
-
std::lock_guard<std::mutex>:是一个模板类,接受一个
std::mutex
或者其他可锁定的互斥量类型作为模板参数。它被设计为一个栈上的对象,用于管理互斥锁的生命周期。 -
构造函数:
std::lock_guard<std::mutex> lock(mtx);
在构造时会锁定mtx
所引用的互斥锁std::mutex
。这确保在构造函数调用时,当前线程会尝试锁定互斥锁,如果互斥锁已经被其他线程锁定,当前线程会被阻塞,直到互斥锁可用。 -
作用域:在
std::lock_guard
的实例lock
被创建时,互斥锁mtx
被锁定。在作用域结束时,无论是通过正常的控制流程离开作用域还是由于异常离开,std::lock_guard
的析构函数会被调用,它会自动释放互斥锁,从而确保在任何情况下都能正确释放资源。 -
RAII 设计模式:这种使用方式符合 RAII 设计模式的理念,即资源获取时即初始化,资源释放时即清理。这种模式不仅简化了代码的编写,还有效地防止了忘记释放锁而造成的资源泄露或死锁问题。
在多线程环境下,使用 std::lock_guard<std::mutex>
是推荐的方式来确保线程安全性,尽量避免手动管理互斥锁的锁定和解锁,从而减少因为人为错误而导致的程序问题。