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

在多线程环境下,单例模式的实现需要保证以下两点:

  1. 全局唯一性:无论程序如何调用,所有线程都能获得同一实例。
  2. 线程安全:实例化过程中不被并发破坏,避免出现“半初始化”的情况。

下面给出一种简洁、现代化且符合C++11及以后标准的实现方案,并对关键点进行逐行说明。

1. 利用局部静态变量的特性

C++11 起,函数内部的局部静态变量在第一次访问时会被安全地初始化(线程安全)。这正好满足单例的“只创建一次”需求。

class Singleton {
public:
    // 公开的获取实例接口
    static Singleton& getInstance() {
        // 这里的static保证仅在第一次调用时初始化一次
        static Singleton instance;
        return instance;
    }

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

    // 示例功能
    void doSomething() {
        std::cout << "Singleton instance address: " << this << std::endl;
    }

private:
    // 构造函数私有化
    Singleton() {
        std::cout << "Singleton constructed." << std::endl;
    }
    ~Singleton() = default;
};

关键点说明

行号 说明
static Singleton instance; 第一次调用 getInstance() 时才会构造 instance,后续调用直接返回已存在对象。C++11 标准保证了此初始化是互斥的,所有线程会等待。
Singleton(const Singleton&) = delete; 防止外部代码通过拷贝或移动得到多份实例,保持全局唯一性。
private: Singleton(); 构造函数私有化,外部无法直接实例化。

2. 延迟加载与销毁

  • 延迟加载:上述实现天然支持懒加载,只有真正需要实例时才创建,节省资源。
  • 销毁顺序:局部静态变量会在程序结束时按逆序析构,确保在程序退出前所有资源被释放。

3. 使用示例

#include <thread>
#include <iostream>

void threadFunc() {
    Singleton& s = Singleton::getInstance();
    s.doSomething();
}

int main() {
    std::thread t1(threadFunc);
    std::thread t2(threadFunc);

    t1.join();
    t2.join();
    return 0;
}

运行结果示例:

Singleton constructed.
Singleton instance address: 0x55e8b2d3e040
Singleton instance address: 0x55e8b2d3e040

可以看到,构造函数只被调用一次,所有线程获得的地址相同,说明实例唯一且线程安全。

4. 进阶:自定义销毁顺序

如果需要在程序结束前手动销毁单例(例如,先关闭网络连接再关闭日志文件),可以采用 std::atexitstd::shared_ptr 配合自定义 deleter:

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton* instance = new Singleton();
        static std::atexit(destroyInstance);
        return *instance;
    }
    // 其它成员同上...
private:
    static void destroyInstance() {
        delete instance;
        instance = nullptr;
    }
};

此方案在 atexit 时释放实例,保证资源按用户指定顺序析构。

5. 总结

  • 利用 C++11 局部静态变量的线程安全初始化可以实现极简、可靠的单例。
  • 禁止拷贝/移动构造、赋值确保唯一性。
  • 如需特殊销毁顺序,可结合 atexit 或智能指针实现。

这套实现已在多线程项目中广泛使用,兼容性强且易于维护。

发表评论