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

单例模式(Singleton)是一种常用的设计模式,确保一个类只有一个实例,并提供全局访问点。在多线程环境中,必须保证实例化过程是线程安全的,否则可能出现多个实例被创建的情况。下面我们将介绍几种在C++中实现线程安全单例的常见方法,并对它们的优缺点进行分析。


1. Meyer’s Singleton(函数内部静态变量)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance; // C++11起,局部静态变量初始化是线程安全的
        return instance;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

优点

  • 简洁明了,几乎不需要任何额外代码。
  • 通过 delete 删除拷贝构造函数和赋值运算符,避免不小心复制。
  • C++11 标准保证局部静态变量的初始化是线程安全的。

缺点

  • 只能在程序运行期间存在,无法在程序结束前显式销毁。
  • 对于多线程的首次调用,可能会有轻微的性能开销(锁的开销)。

2. 双重检查锁(Double-Check Locking)

#include <atomic>
#include <mutex>

class Singleton {
public:
    static Singleton* instance() {
        Singleton* tmp = instance_.load(std::memory_order_acquire);
        if (!tmp) {
            std::lock_guard<std::mutex> lock(mutex_);
            tmp = instance_.load(std::memory_order_relaxed);
            if (!tmp) {
                tmp = new Singleton;
                instance_.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
};

std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;

优点

  • 延迟实例化(lazy initialization)与线程安全兼顾。
  • 在第一次实例化后,后续获取实例时不需要加锁,性能更高。

缺点

  • 代码较为复杂,易出错。
  • 需要手动管理实例生命周期(需要显式删除)。

3. 静态成员指针与 std::call_once

#include <mutex>

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag_, []() { instance_ = new Singleton; });
        return *instance_;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

    static Singleton* instance_;
    static std::once_flag initFlag_;
};

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;

优点

  • 利用标准库提供的 std::call_oncestd::once_flag 实现一次性初始化,代码相对简洁。
  • 与双重检查锁相比更安全,避免了因指针未对齐导致的竞态。

缺点

  • 需要手动管理实例指针,可能出现内存泄漏。
  • 同样无法显式销毁实例。

4. 经典的“静态成员变量 + 互斥量”实现

class Singleton {
public:
    static Singleton& instance() {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!instance_) {
            instance_ = new Singleton;
        }
        return *instance_;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

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

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

优点

  • 简单直观,适用于 C++11 之前的标准(如 C++03)。
  • 通过 std::mutex 保证线程安全。

缺点

  • 每次获取实例时都要加锁,导致性能下降。
  • 需要手动销毁,且在多线程环境下需要注意析构时机。

5. 对比与选择

方法 线程安全保证 初始化时机 代码复杂度 生命周期管理 适用场景
Meyer’s Singleton 通过局部静态实现 程序启动时首次调用 简单 隐式销毁 C++11+
双重检查锁 原子 + mutex 延迟 手动 高并发后期访问
call_once std::call_once 延迟 中等 手动 需要一次性初始化
静态指针 + mutex 互斥锁 延迟 简单 手动 兼容旧标准
  • 如果你使用 C++11 及以后版本,推荐使用 Meyer’s Singleton。它最简洁,性能最优,且线程安全保证可靠。
  • 如果你在 C++11 之前,或者想在运行时动态决定是否实例化,call_once双重检查锁 是较好的选择。
  • 如果你需要在程序结束前显式销毁实例,可以结合 std::unique_ptr 与自定义 std::atexit 函数来完成。

6. 参考代码:完整可运行示例

#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
#include <memory>
#include <chrono>

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

    void doWork(int id) {
        std::lock_guard<std::mutex> lock(mutex_);
        std::cout << "Thread " << id << " is using singleton instance at " << this << std::endl;
    }

private:
    ThreadSafeSingleton() {
        std::cout << "Singleton constructed at " << this << std::endl;
    }
    ~ThreadSafeSingleton() = default;

    ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
    ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;

    std::mutex mutex_;
};

void worker(int id) {
    auto& singleton = ThreadSafeSingleton::getInstance();
    singleton.doWork(id);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
}

int main() {
    const int threadCount = 10;
    std::vector<std::thread> threads;

    for (int i = 0; i < threadCount; ++i) {
        threads.emplace_back(worker, i);
    }

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

    return 0;
}

运行结果示例(所有线程共享同一个地址):

Singleton constructed at 0x7ffc1d0b2d30
Thread 0 is using singleton instance at 0x7ffc1d0b2d30
Thread 1 is using singleton instance at 0x7ffc1d0b2d30
...

7. 小结

  • 单例模式在 C++ 中实现不难,但在多线程环境中需要特别注意初始化的线程安全性。
  • C++11 标准提供了最简洁的 Meyer's Singleton 方案,利用局部静态变量实现线程安全的延迟初始化。
  • 其它方案(双重检查锁、std::call_once、手动锁)适用于对初始化时机、生命周期控制或兼容旧标准的特殊需求。
  • 在实际项目中,应根据需求、编译器支持程度、性能考虑以及代码维护成本综合评估,选择最合适的实现方式。

发表评论