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

在 C++11 及以后版本中,线程安全的单例模式可以借助语言本身的特性实现,既简洁又高效。下面从多种实现思路出发,逐步演示最推荐的实现方式,并给出注意事项。

1. 理解需求

  • 全局唯一:类实例在整个程序生命周期内只存在一次。
  • 线程安全:多线程并发访问时不出现竞态条件或数据损坏。
  • 延迟初始化:只有在第一次使用时才真正创建实例。
  • 资源回收:程序结束时自动销毁实例。

2. C++11 语义:函数内部静态局部对象

C++11 引入了对函数内部静态局部对象的初始化进行线程安全保证的标准声明。利用这一特性,我们可以极简地实现单例:

class ThreadSafeSingleton {
public:
    static ThreadSafeSingleton& getInstance() {
        static ThreadSafeSingleton instance; // C++11 确保线程安全
        return instance;
    }

    // 禁止拷贝与赋值
    ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
    ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;

    void doSomething() {
        // 业务逻辑
    }

private:
    ThreadSafeSingleton() = default;
    ~ThreadSafeSingleton() = default;
};

关键点解释

  • static ThreadSafeSingleton instance; 在第一次调用 getInstance() 时完成初始化,随后所有线程均使用同一实例。
  • 编译器保证了对该静态对象的构造是线程安全的(C++11 标准规定了这种实现)。
  • 通过删除拷贝构造和赋值运算符,避免了意外复制。

3. 传统实现(适用于 C++03 或更早)

如果你必须在 C++11 之前编写代码,可使用双重检查锁(Double-Checked Locking)与互斥锁结合。示例:

#include <mutex>

class ThreadSafeSingleton {
public:
    static ThreadSafeSingleton* getInstance() {
        if (!instance_) {                      // 第一检查
            std::lock_guard<std::mutex> lock(mutex_);
            if (!instance_) {                  // 第二检查
                instance_ = new ThreadSafeSingleton();
            }
        }
        return instance_;
    }

    void doSomething() { /* ... */ }

private:
    ThreadSafeSingleton() = default;
    ~ThreadSafeSingleton() = default;

    static ThreadSafeSingleton* instance_;
    static std::mutex mutex_;
};

ThreadSafeSingleton* ThreadSafeSingleton::instance_ = nullptr;
std::mutex ThreadSafeSingleton::mutex_;

注意

  • 该实现需要保证 instance_ 的可见性,即在多线程中 new 后的写操作必须对其他线程可见。通常使用 std::atomicvolatilestd::memory_order 约束实现。
  • 更现代的做法是使用 std::call_once,其语义更安全、更易读。
#include <mutex>

class ThreadSafeSingleton {
public:
    static ThreadSafeSingleton& getInstance() {
        std::call_once(flag_, [](){ instance_ = new ThreadSafeSingleton(); });
        return *instance_;
    }

private:
    static ThreadSafeSingleton* instance_;
    static std::once_flag flag_;
};

ThreadSafeSingleton* ThreadSafeSingleton::instance_ = nullptr;
std::once_flag ThreadSafeSingleton::flag_;

4. 资源管理与销毁

  • 静态局部对象:C++ 会在程序退出时自动销毁实例,无需手动释放。
  • new 分配:如果使用 new,请在 atexit 或手动 delete。但建议使用静态局部对象或 std::unique_ptr 自动管理。

5. 进一步提升

  • 懒加载 + 线程安全:C++17 的 inline 变量或 std::shared_ptrstd::atomic 结合,可以实现更细粒度的控制。
  • 多线程性能:如果单例对象内部需要频繁被并发访问,建议在内部使用细粒度锁或读写锁,避免整个单例成为瓶颈。

6. 代码完整示例(C++17 版)

#include <iostream>
#include <mutex>
#include <thread>

class Logger {
public:
    static Logger& instance() {
        static Logger logger;          // C++17 保证线程安全初始化
        return logger;
    }

    void log(const std::string& msg) {
        std::lock_guard<std::mutex> guard(mutex_);
        std::cout << "[" << std::this_thread::get_id() << "] " << msg << '\n';
    }

private:
    Logger() = default;
    ~Logger() = default;
    std::mutex mutex_;
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
};

void worker(int id) {
    for (int i = 0; i < 5; ++i) {
        Logger::instance().log("Worker " + std::to_string(id) + " iteration " + std::to_string(i));
    }
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);
    t1.join(); t2.join();
    return 0;
}

运行结果示例:

[0x7f9c9c000700] Worker 1 iteration 0
[0x7f9c9c000700] Worker 2 iteration 0
[0x7f9c9c000700] Worker 1 iteration 1
[0x7f9c9c000700] Worker 2 iteration 1
...

7. 小结

  • 推荐方案:C++11 及以后版本使用函数内部静态局部对象,代码简洁、语义清晰。
  • 旧标准兼容:使用 std::call_once 或双重检查锁+互斥锁。
  • 资源安全:利用 RAII 自动销毁,避免内存泄漏。
  • 性能:在高并发场景下,考虑内部锁的粒度。

通过上述实现,你可以在任何 C++ 项目中安全、简洁地使用单例模式,避免多线程竞争导致的不可预测行为。

发表评论