设计模式中单例模式中懒汉模式的问题

embedded/2024/12/27 21:01:50/

设计模式单例模式中懒汉模式的问题

今天在项目中遇到了要使用懒汉模式的问题。百度之后,发现还有很多细节是自己之前没有见过的。于是记录一下。下面是在AI助手中的说明。

单例模式的懒汉模式(Lazy Singleton)是在需要时才创建实例,而不是在程序启动时就创建。懒汉模式常见的实现方式存在一些问题,尤其是在多线程环境中。

下面将详细讲解懒汉模式的优缺点及其问题。

1.懒汉模式的实现通常如下:

class Singleton {
private:static Singleton* instance;// 私有构造函数,防止外部创建实例Singleton() {}public:// 获取实例的公共方法static Singleton* getInstance() {if (instance == nullptr) {instance = new Singleton();}return instance;}// 防止拷贝构造和赋值操作Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};// 静态成员初始化
Singleton* Singleton::instance = nullptr;

懒汉模式的问题

  1. 线程安全问题

    在多线程环境中,懒汉模式存在问题。当多个线程同时调用 getInstance() 方法时,可能会导致多个线程同时判断 instance == nullptrtrue,然后都去创建实例,导致多个实例的创建。为了解决这个问题,可以使用互斥锁(mutex)来保证线程安全,但是这样会降低性能。

修改后的线程安全实现(使用 mutex):

#include <mutex>class Singleton {
private:static Singleton* instance;static std::mutex mtx; // 用于保护实例的互斥锁Singleton() {}public:static Singleton* getInstance() {if (instance == nullptr) {std::lock_guard<std::mutex> lock(mtx);if (instance == nullptr) {instance = new Singleton();}}return instance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

这样可以避免多个线程同时创建多个实例,但性能会受到影响,因为每次获取实例时都需要加锁。在上述实现中,我们使用了双重检查锁定(Double-Checked Locking)模式来提高性能。即第一次检查 instance == nullptr 时不加锁,只有在需要创建实例时才加锁。

2. 使用 std::atomic 确保内存可见性和防止指令重排

C++11 提供了 std::atomic 来确保操作的原子性并避免指令重排。在单例模式中,使用 std::atomic 可以确保对 instance 的写操作对所有线程可见,并且避免因指令重排引发的问题。

为了保证 instance 的初始化过程对所有线程是安全的,我们可以使用 std::atomic 作为 instance 的类型。这样,C++11 保证了原子操作的内存顺序,防止了指令重排问题。

#include <atomic>class Singleton {
private:static std::atomic<Singleton*> instance;Singleton() {}public:static Singleton* getInstance() {Singleton* temp = instance.load(std::memory_order_acquire);if (temp == nullptr) {std::lock_guard<std::mutex> lock(mtx);temp = instance.load(std::memory_order_relaxed);if (temp == nullptr) {temp = new Singleton();instance.store(temp, std::memory_order_release);}}return temp;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};std::atomic<Singleton*> Singleton::instance{nullptr};

3.多线程性能开销

在多线程环境下,即使是双重检查锁定,也会引入性能开销。锁的争用可能会导致性能瓶颈,尤其是在实例已经创建时,锁的机制会影响到性能。因此,如果创建的实例较为复杂且开销较大,这种性能问题更加明显。

改进方法

  1. 使用 std::call_once
    C++11 提供了 std::call_once,它可以确保某个代码块在多线程中只会被执行一次。使用它可以避免锁的开销。

    改进后的实现:

    cpp#include <mutex>class Singleton {
    private:static Singleton* instance;static std::once_flag flag;  // 标记Singleton() {}public:static Singleton* getInstance() {std::call_once(flag, []() {instance = new Singleton();});return instance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
    };Singleton* Singleton::instance = nullptr;
    std::once_flag Singleton::flag;
    

    这种方式在多线程中比加锁性能更好。

  2. 使用 static 局部变量
    最简单且性能最优的方法是利用 C++ 的静态局部变量特性。C++11 标准保证了静态局部变量的初始化是线程安全的,且只会初始化一次。这是推荐的懒汉模式实现方式。

    cppclass Singleton {
    private:Singleton() {}public:static Singleton* getInstance() {static Singleton instance;  // 线程安全的静态局部变量return &instance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
    };
    

    这种方式不需要显式加锁,且由编译器保证线程安全,并且只会初始化一次,性能非常好。

总结

懒汉模式在多线程环境下需要特别小心,常见的问题包括线程安全问题、内存泄漏、性能开销等。为了确保线程安全并避免性能问题,推荐使用 std::call_once 或 C++11 中的静态局部变量来实现单例模式。通过这些改进,可以大幅度提升懒汉模式的效率和安全性。


http://www.ppmy.cn/embedded/149266.html

相关文章

用微软365邮箱收发邮件【azure-应用注册】

前置条件&#xff1a; - 有一个365邮箱&#xff0c;配置好许可证 - 在azure portal里有Microsoft Entra ID &#xff0c;注册相关应用时graph API赋权 - 应用的应用程序(客户端) ID&#xff0c;目录(租户) ID&#xff0c;客户端的密码&#xff0c;邮箱的id&#xff0c;名称 …

Django 模型管理器中自定义方法和添加导出功能

在 Django 中,模型管理器提供了一种扩展模型行为的方式。您可以重写或添加自定义方法,以满足特定的业务需求。在本文中,我们将探讨如何在模型管理器中自定义方法,并提供一些常见的用例。此外,我们还将介绍如何在管理员界面中添加导出数据为 CSV 文件的功能。 什么是模型管理器…

基于PWLCM混沌映射的麋鹿群优化算法(Elk herd optimizer,EHO)的多无人机协同路径规划,MATLAB代码

一、麋鹿群优化算法EHO 基本概念 麋鹿群优化算法&#xff08;EHO&#xff0c;Elephant Herding Optimization&#xff09;是2024年提出的一种启发式优化算法&#xff0c;它的灵感来自麋鹿群的繁殖过程。麋鹿有两个主要的繁殖季节&#xff1a;发情和产犊。在发情季节&#xff0…

Linux零基础速成篇一(理论+实操)

前言&#xff1a;本教程适合Linux零基础学习&#xff0c;也适合Linux期末考试的小伙伴&#xff0c;从头到尾理论与实操相结合&#xff0c;让你快速对Linux进行了解和掌握。 一、Linux概述 为什么要学习Linux操作系统&#xff1f; 完全免费-开源 任何用户均可下载使用 安全…

Python 自动化 打开网站 填表登陆 例子

图样 简价&#xff1a; 简要说明这个程序的功能&#xff1a; 1. **基本功能**&#xff1a; - 自动打开网站 - 自动填写登录信息&#xff08;号、公司名称、密码&#xff09; - 显示半透明状态窗口实时提示操作进度 2. **操作流程**&#xff1a; - 打开网站后自动…

云原生周刊:利用 eBPF 增强 K8s

开源项目推荐 Slurm-operator Slurm-operator 是一个高效可扩展的框架&#xff0c;用于在 K8s 环境中部署和运行 Slurm 工作负载。 它结合了 Slurm 的可靠性和 Kubernetes 的灵活性&#xff0c;支持快速部署 Slurm 集群、动态扩展 HPC 工作负载&#xff0c;并提供高度灵活的定…

微服务——服务通信与接口设计

1、微服务之间常见的通信方式有哪些&#xff1f;请对比它们的优缺点。 通信方式优点缺点RESTful API技术栈无关&#xff0c;兼容性强易于调试&#xff0c;适合跨平台调用性能较低&#xff08;基于 HTTP&#xff09;&#xff0c;延迟较大数据传输量较大RPC高性能&#xff0c;低…

Java的垃圾回收机制介绍、工作原理、算法及分析调优

Java的垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;是Java虚拟机&#xff08;JVM&#xff09;提供的一种自动内存管理机制&#xff0c;用于自动回收不再使用的内存空间&#xff0c;以避免内存泄露和内存溢出等问题。下面主要介绍Java垃圾回收的基本概念、…