**如何在C++17中实现一个线程安全的懒加载单例?**

在 C++17 之前,常见的单例实现方式有三种:懒汉式(双重检查锁定)、饿汉式、Meyers 单例。每种方式都有自己的优缺点,尤其在多线程环境下的安全性更是重要。下面我们将逐步演示如何在 C++17 中使用 std::call_oncestd::once_flag 结合 std::unique_ptr,实现一个线程安全、懒加载的单例。

1. 设计思路

  • 懒加载:实例只在第一次需要时创建,节省资源。
  • 线程安全:使用 std::call_once 确保实例只被创建一次,即使多个线程同时请求。
  • 资源释放:使用 std::unique_ptr 自动管理生命周期,避免手动 delete

2. 代码实现

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

class Singleton {
public:
    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        // std::call_once 保证只执行一次初始化
        std::call_once(initFlag_, []() {
            instance_ = std::unique_ptr <Singleton>(new Singleton());
        });
        return *instance_;
    }

    void doWork() {
        std::cout << "线程 " << std::this_thread::get_id() << " 正在使用单例实例,地址: " << this << "\n";
    }

private:
    Singleton() { 
        std::cout << "Singleton 构造函数调用,地址: " << this << "\n";
    }
    ~Singleton() {
        std::cout << "Singleton 析构函数调用,地址: " << this << "\n";
    }

    static std::unique_ptr <Singleton> instance_;
    static std::once_flag initFlag_;
};

std::unique_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;

3. 多线程测试

void worker() {
    // 随机延迟模拟并发情况
    std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 100));
    Singleton::getInstance().doWork();
}

int main() {
    std::srand(static_cast <unsigned>(std::time(nullptr)));
    std::vector<std::thread> threads;

    // 创建 10 个线程同时请求单例
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(worker);
    }

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

    std::cout << "所有线程已结束。\n";
    return 0;
}

4. 运行结果(示例)

Singleton 构造函数调用,地址: 0x55f8c0b5c2e0
线程 140355345816448 正在使用单例实例,地址: 0x55f8c0b5c2e0
线程 140355337423744 正在使用单例实例,地址: 0x55f8c0b5c2e0
...
所有线程已结束。
Singleton 析构函数调用,地址: 0x55f8c0b5c2e0

可以看到:

  • 构造函数仅被调用一次,地址一致;
  • 所有线程共享同一实例;
  • 在程序退出时,单例被正确析构。

5. 关键点总结

技术 作用 说明
std::call_once 保证单次初始化 线程安全地执行一次初始化代码
std::once_flag 伴随 call_once 使用 记录初始化状态
std::unique_ptr 自动释放 避免手动 delete
delete 拷贝构造/赋值 防止多实例 确保唯一实例

6. 进一步扩展

  • 懒加载与自毁:如果想在程序结束后显式销毁单例,可以使用 std::shared_ptrstd::weak_ptr,或在 Singleton 的析构中注册 std::atexit 进行清理。
  • 模板单例:把 Singleton 设计成模板类 `Singleton `,适用于多种对象。
  • 延迟初始化的对象:若单例内部资源本身昂贵,可以再使用 std::optionalstd::shared_ptr 延迟加载。

通过上述实现,你可以在任何 C++17 项目中快速、安全地使用单例模式,满足多线程并发访问的需求。

发表评论