如何在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;
    ~Singleton() = default;
};
  • 优点:代码最简洁,编译器保证线程安全;延迟初始化,实例仅在第一次使用时创建。
  • 缺点:无法提前销毁实例;如果构造函数抛异常,后续调用可能再次尝试创建。

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

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

    ~Singleton() {
        delete instance_;
    }

private:
    Singleton() = default;
    static Singleton* instance_;
    static std::mutex mutex_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
  • 优点:可以手动控制实例生命周期,适用于需要在程序结束前销毁单例的场景。
  • 缺点:实现较为繁琐,易出错;在一些编译器下可能不够严格的内存屏障,导致可见性问题。

3. 静态局部对象 + std::call_once

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

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

private:
    Singleton() = default;
    static std::unique_ptr <Singleton> instance_;
    static std::once_flag initFlag_;
};

std::unique_ptr <Singleton> Singleton::instance_;
std::once_flag Singleton::initFlag_;
  • 优点:显式控制初始化时机;call_once 确保线程安全且只调用一次。
  • 缺点:稍显冗长;需要额外维护 std::unique_ptr

4. Meyers 单例与 std::shared_ptr 的组合

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        static std::shared_ptr <Singleton> instance(new Singleton);
        return instance;
    }
private:
    Singleton() = default;
};
  • 优点:返回 shared_ptr,可以方便地进行引用计数管理,避免手动 delete。
  • 缺点:多余的引用计数开销;实例不容易提前销毁。

5. 适配 std::atomic 的单例

class Singleton {
public:
    static Singleton& getInstance() {
        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;
    }

private:
    Singleton() = default;
    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
};

std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
  • 优点:避免了在 call_once 或局部静态变量中使用 new 的隐式动态分配。
  • 缺点:实现最为复杂,容易引入微妙的内存可见性错误。

选择建议

  • 最快捷:使用 C++11 的静态局部变量,适合绝大多数业务场景。
  • 需要手动销毁:使用双重检查锁或 std::call_once + unique_ptr
  • 对性能极致追求:使用 std::atomic 的实现,但需保证线程模型正确。

总之,C++11 及以后版本已经大大简化了线程安全单例的实现,推荐使用最简洁的方式,除非有特殊的资源管理需求。

发表评论