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

在多线程环境下,单例模式需要保证同一时刻只能有一个实例被创建,并且在多个线程同时访问时不会出现竞争条件。下面通过几种典型实现方式,介绍如何在C++中编写线程安全的单例。

1. 局部静态变量(C++11及以后)

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;   // C++11保证线程安全
        return instance;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() = default;
};

C++11之后的编译器会在第一次调用getInstance()时以线程安全的方式初始化instance。这是一种最简洁、最安全的实现。

2. 双重检查锁(DCLP)

class Singleton {
public:
    static Singleton* getInstance() {
        if (!instance) {                    // 第一检查
            std::lock_guard<std::mutex> lock(mtx);
            if (!instance) {                // 第二检查
                instance = new Singleton();
            }
        }
        return instance;
    }
private:
    Singleton() = default;
    static Singleton* instance;
    static std::mutex mtx;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

双重检查锁可以减少锁的使用次数,但需要注意内存模型和对象构造的可见性。C++11提供了std::atomic可用于保证可见性。

3. std::call_oncestd::once_flag

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(flag, [](){ instance = new Singleton(); });
        return *instance;
    }
private:
    Singleton() = default;
    static Singleton* instance;
    static std::once_flag flag;
};
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::flag;

std::call_once保证给定的初始化函数只会被调用一次,内部使用了线程安全的原子操作,适合需要显式控制初始化逻辑的场景。

4. 静态局部变量 + std::shared_ptr

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        static std::shared_ptr <Singleton> instance(new Singleton(), [](Singleton*){});
        return instance;
    }
private:
    Singleton() = default;
};

使用std::shared_ptr可以在程序退出时自动释放资源,避免悬空指针。

5. Meyers单例与延迟销毁

局部静态变量在程序退出时会自动销毁,避免了手动释放。若想控制销毁时机,可以结合std::unique_ptr与自定义析构器。

6. 线程安全的懒汉式与饿汉式比较

特性 懒汉式(如getInstance() 饿汉式(在程序启动时创建)
延迟加载
内存占用 需要时才占用 立即占用
初始化顺序 线程安全(C++11) 无竞争问题
可测试性 可能导致单例在单元测试中不易重置 简单

小结

  • 对于大多数现代C++项目,局部静态变量(Meyers单例)是最推荐的实现方式,简洁且线程安全。
  • 若需要自定义销毁逻辑或懒加载后手动释放,std::call_once或双重检查锁是可选方案,但实现更复杂。
  • 在设计单例时,也要考虑单例对象的生命周期、资源管理和测试友好性,避免单例导致的全局状态耦合。

掌握上述几种实现后,你就能在多线程C++项目中安全、灵活地使用单例模式。

发表评论