如何在C++中实现一个线程安全的单例模式

在多线程环境下,单例模式常常需要保证只有一个实例被创建,并且在并发访问时不出现竞争条件。C++11 引入了对 std::call_oncestd::once_flag 的支持,使得实现线程安全单例变得异常简洁。下面给出一个完整的示例,并对其工作原理进行详细说明。

// Singleton.h
#pragma once
#include <mutex>
#include <memory>

template <typename T>
class Singleton {
public:
    // 获取单例实例
    static T& instance() {
        std::call_once(initFlag_, []() {
            // 使用 std::unique_ptr 防止内存泄漏
            instancePtr_.reset(new T());
        });
        return *instancePtr_;
    }

    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

protected:
    Singleton() = default;
    virtual ~Singleton() = default;

private:
    static std::once_flag initFlag_;
    static std::unique_ptr <T> instancePtr_;
};

// Singleton.cpp
#include "Singleton.h"

template <typename T>
std::once_flag Singleton <T>::initFlag_;

template <typename T>
std::unique_ptr <T> Singleton<T>::instancePtr_;

使用示例:

// MyService.h
#pragma once
#include <iostream>
#include <string>

class MyService {
public:
    void doWork() {
        std::cout << "Service doing work on thread " << std::this_thread::get_id() << std::endl;
    }
};

// main.cpp
#include "Singleton.h"
#include "MyService.h"
#include <thread>
#include <vector>

int main() {
    auto worker = [](){
        // 访问单例
        MyService& svc = Singleton <MyService>::instance();
        svc.doWork();
    };

    // 启动多个线程并行调用
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(worker);
    }

    for (auto& t : threads) {
        t.join();
    }
    return 0;
}

关键点说明

  1. std::call_once + std::once_flag
    std::call_once 确保传入的 lambda 只会被执行一次,即使有多个线程同时调用。once_flag 维护状态,内部实现使用原子操作和互斥锁,保证线程安全。

  2. std::unique_ptr
    用来管理单例对象的生命周期。由于 instancePtr_ 是静态成员,它在程序结束时会被销毁,释放资源。若使用裸指针,可能会出现未定义行为。

  3. 模板化单例
    通过模板将单例模式泛化,任何类都可以通过 `Singleton

    ::instance()` 获得线程安全的单例实例。
  4. 禁用拷贝
    单例不应被复制或赋值,删除拷贝构造和赋值运算符能避免不小心的错误。

  5. 构造函数和析构函数
    保护构造函数,确保外部不能直接实例化。析构函数默认即可,因为 unique_ptr 会自动析构。

何时需要手工实现

如果项目使用的是 C++03 或更老版本,或者不想依赖标准库的线程支持,常见做法是使用双重检查锁(Double-Checked Locking)或静态局部变量初始化(在 C++11 之前不可在多线程环境下保证安全)。在现代 C++ 环境下,上面的方法已足够稳健且代码可读性高。

通过以上实现,你可以在任何需要单例的场景下,安全地在多线程程序中使用 `Singleton

::instance()`,无需担心实例化时的竞争问题。

发表评论