单例模式:多线程环境下的实现与应用
立即解锁
发布时间: 2025-08-21 01:42:09 阅读量: 1 订阅数: 4 


现代C++设计:泛型编程与设计模式的应用
# 单例模式:多线程环境下的实现与应用
## 1. 多线程环境下的单例问题
在多线程应用中,单例模式会面临一些挑战。假设一个应用刚启动,有两个线程同时访问如下单例:
```cpp
Singleton& Singleton::Instance()
{
if (!pInstance_)
// 1
{
pInstance_ = new Singleton;
// 2
}
return *pInstance_;
// 3
}
```
第一个线程进入`Instance`函数,测试`if`条件。由于是首次访问,`pInstance_`为`null`,线程到达标记为`// 2`的行,准备调用`new`操作符。此时,操作系统调度器可能会中断第一个线程,将控制权交给第二个线程。
第二个线程进入`Singleton::Instance()`,同样发现`pInstance_`为`null`,因为第一个线程还没来得及修改它。接着,第二个线程调用`new`操作符,成功赋值`pInstance_`。
当第一个线程恢复执行时,它会继续执行标记为`// 2`的行,再次赋值`pInstance_`。最终,会创建两个`Singleton`对象,其中一个肯定会发生内存泄漏,应用也会陷入混乱。这就是经典的竞态条件问题。
## 2. 双检查锁定模式
### 2.1 简单加锁方案
为了解决上述竞态条件问题,一个简单的方案是加锁:
```cpp
Singleton& Singleton::Instance()
{
// mutex_ 是一个互斥锁对象
// Lock 管理互斥锁
Lock guard(mutex_);
if (!pInstance_)
{
pInstance_ = new Singleton;
}
return *pInstance_;
}
```
这种方案虽然解决了竞态条件问题,但效率较低。每次调用`Instance`函数都需要进行加锁和解锁操作,而竞态条件实际上只在单例对象创建时才会出现。这些操作的开销通常比简单的`if (!pInstance_)`测试大得多。
### 2.2 尝试减少开销的方案
```cpp
Singleton& Singleton::Instance()
{
if (!pInstance_)
{
Lock guard(mutex_);
pInstance_ = new Singleton;
}
return *pInstance_;
}
```
这种方案虽然减少了开销,但竞态条件又回来了。第一个线程通过`if`测试后,在进入同步代码块之前可能被中断,第二个线程也通过`if`测试并完成单例对象的创建。当第一个线程恢复执行时,会再次创建一个单例对象。
### 2.3 双检查锁定模式
双检查锁定模式的思路是:先检查条件,进入同步代码块,然后再次检查条件。
```cpp
Singleton& Singleton::Instance()
{
if (!pInstance_)
// 1
{
// 2
Guard myGuard(lock_);
// 3
if (!pInstance_)
// 4
{
pInstance_ = new Singleton;
}
}
return *pInstance_;
}
```
第一个测试快速且粗略,如果单例对象已经存在,直接返回。如果不存在,则进入同步代码块进行更精确的检查。在同步代码块中,只有一个线程能进入,第一个进入的线程会初始化单例对象,其他线程再次检查时会发现对象已经初始化,不会再创建新对象。
然而,在某些对称多处理器环境(具有所谓的宽松内存模型)中,双检查锁定模式可能会失效。因为内存写入操作可能会以地址递增的顺序批量提交到主内存,而不是按时间顺序。这可能导致一个处理器对`pInstance_`的赋值操作在单例对象完全初始化之前就发生。因此,在实现双检查锁定模式之前,需要检查编译器文档,或者使用`volatile`修饰`pInstance_`。
## 3. SingletonHolder类模板
### 3.1 策略分解
为了实现单例模式,我们可以将其分解为三个主要策略:
1. **创建策略**:可以通过多种方式创建单例对象,通常使用`new`操作符。将创建操作作为一个策略隔离出来,有助于创建多态对象。
2. **生命周期策略**:
- 遵循C++规则(最后创建,最先销毁)
- 循环(Phoenix单例)
- 用户控制(具有寿命的单例)
- 无限期(“泄漏”单例,即对象永远不会被销毁)
3. **线程模型策略**:单线程、标准多线程(使用互斥锁和双检查锁定模式)或使用非便携式线程模型。
### 3.2 策略要求
- **创建策略**:必须提供`Create`和`Destroy`两个静态成员函数,用于创建和销毁对象。
```cpp
T* pObj = Creator<T>::Create();
Creator<T>::Destroy(pObj);
```
- **生命周期策略**:需要安排单例对象的销毁时间,并决定在违反单例对象生命周期规则时采取的行动。具体来说,需要实现`ScheduleDestruction`和`OnDeadReference`两个函数。
```cpp
void (*pDestructionFunction)();
...
Lifetime<T>::ScheduleDestruction(pDestructionFunction);
Lifetime<T>::OnDeadReference();
```
- **线程模型策略**:提供一个内部类`Lock`,用于实现线程同步。
### 3.3 Single
0
0
复制全文
相关推荐









