C++多线程环境下的单例类对象创建

使用C++无锁编程实现多线程下的单例模式

贺志国
2023.8.1

一、尺寸较小的类单例对象创建

如果待创建的单例类SingletonForMultithread内包含的成员变量较少,整个类占用的内存空间较小,则可以使用如下方法来创建单例对象(如果类的尺寸较大,静态变量存储栈区无法容纳该类的单例对象,则禁止使用该方法。例如:64位Linux系统默认栈的最大空间为8 MB,64位Windows系统默认栈的最大空间为1 MB,当待创建的单例对象尺寸接近或超过上述栈的默认存储空间时,如使用该方法创建则会导致程序崩溃):

class SmallSingletonForMultithread {public:static SmallSingletonForMultithread& GetInstance() {static SmallSingletonForMultithread instance;return instance;}private:SmallSingletonForMultithread() = default;~SmallSingletonForMultithread() = default;SmallSingletonForMultithread(const SmallSingletonForMultithread&) = delete;SmallSingletonForMultithread& operator=(const SmallSingletonForMultithread&) = delete;SmallSingletonForMultithread(SmallSingletonForMultithread&&) = delete;SmallSingletonForMultithread& operator=(SmallSingletonForMultithread&&) = delete;
};

二、尺寸较大的类单例对象创建(要求显式调用销毁函数来避免内存泄漏)

在实际工作中,由于某些单例类的尺寸较大,静态变量存储栈区无法容纳该单例对象,因此无法使用上述方法来创建单例对象,这时需要使用new在堆区动态创建单例对象。为了避免多线程环境下对于单例对象的抢夺,可使用C++无锁编程来实现。需要付出的代价就是,最后一个调用者需要显式地调用销毁函数DestoryInstance来避免内存泄漏,示例代码如下所示:

#include <atomic>
#include <cassert>
#include <mutex>class SingletonForMultithread {public:static SingletonForMultithread* GetInstance() {if (!instance_.load(std::memory_order_acquire)) {auto* new_ptr = new SingletonForMultithread;SingletonForMultithread* old_ptr = nullptr;if (!instance_.compare_exchange_strong(old_ptr, new_ptr,std::memory_order_release,std::memory_order_relaxed)) {// If the CAS operation fails, another thread has created a singleton// object, and it's necessary to delete the temporary object created by// the current thread.delete new_ptr;new_ptr = nullptr;}}return instance_.load(std::memory_order_relaxed);}static void DestoryInstance() {if (instance_.load(std::memory_order_acquire)) {auto* old_ptr = instance_.load(std::memory_order_relaxed);SingletonForMultithread* new_ptr = nullptr;if (instance_.compare_exchange_strong(old_ptr, new_ptr,std::memory_order_release,std::memory_order_relaxed)) {// If the CAS operation succeeds, the current thread obtains the// original object and can safely delete it.delete old_ptr;old_ptr = nullptr;}}}private:SingletonForMultithread() = default;~SingletonForMultithread() = default;SingletonForMultithread(const SingletonForMultithread&) = delete;SingletonForMultithread& operator=(const SingletonForMultithread&) = delete;SingletonForMultithread(SingletonForMultithread&&) = delete;SingletonForMultithread& operator=(SingletonForMultithread&&) = delete;private:static std::atomic<SingletonForMultithread*> instance_;
};// Static member variable initialization
std::atomic<SingletonForMultithread*> SingletonForMultithread::instance_;int main() {auto* singleton = SingletonForMultithread::GetInstance();assert(singleton != nullptr);singleton->DestoryInstance();return 0;
}

三、尺寸较大的类单例对象创建(使用std::unique_ptr<T>std::call_once实现)

很多时候,我们无法显式地调用销毁函数来避免内存泄漏,这时就可借助std::unique_ptr<T>std::call_once来实现,示例代码如下:

#include <cassert>
#include <memory>
#include <mutex>class SingletonForMultithread {public:~SingletonForMultithread() = default;static SingletonForMultithread* GetInstance() {static std::unique_ptr<SingletonForMultithread> instance;static std::once_flag only_once;std::call_once(only_once,[]() { instance.reset(new (std::nothrow) SingletonForMultithread); });return instance.get();}private:SingletonForMultithread() = default;SingletonForMultithread(const SingletonForMultithread&) = delete;SingletonForMultithread& operator=(const SingletonForMultithread&) = delete;SingletonForMultithread(SingletonForMultithread&&) = delete;SingletonForMultithread& operator=(SingletonForMultithread&&) = delete;
};int main() {auto* singleton = SingletonForMultithread::GetInstance();assert(singleton != nullptr);return 0;
}

但我在Ubuntu 20.04系统上使用GCC 9.4.0似乎无法正常完成任务,会抛出异常,产生core dump,原因暂不详。
gcc
core dump

四、尺寸较大的类单例对象创建(使用std::unique_ptr<T>std::atomic_flag实现)

第三节借助std::unique_ptr<T>std::call_once来实现单例对象的创建,同时避免显式地调用销毁函数来避免内存泄漏。这种方法在Ubuntu 20.04系统上使用GCC 9.4.0实现时似乎会导致程序core dump。于是我们使用std::atomic_flag替换std::call_once来完成任务。基本思想如下:首先定义一个静态的无锁标志变量std::atomic_flag start_flag,并将其初始值设置为ATOMIC_FLAG_INIT。第一次调用start_flag.test_and_set(std::memory_order_relaxed)函数时,由于start_flag的状态是ATOMIC_FLAG_INIT,该函数返回false,于是可调用instance.reset(new SingletonForMultithread)创建单例对象。第二次直至第N次调用start_flag.test_and_set(std::memory_order_relaxed)函数时,因为start_flag的状态已被设置,该函数返回true,创建单例对象的语句instance.reset(new SingletonForMultithread)永远不会被再次执行,这就达到了只创建一次的目的。同时,因为使用静态的智能指针变量std::unique_ptr<SingletonForMultithread> instance来管理单例对象,于是不再需要显式地回收内存,只要程序结束,静态变量自动清除,智能指针对象instance会在其析构函数中释放内存。

由于new运算符创建单例对象可能耗时较长,为了避免其他线程在单例对象创建到一半的过程中读取到不完整的对象,导致未定义的行为,我们使用另一个原子变量std::atomic<bool> finished来确保创建动作已正确完成,不选用另一个无锁标志变量std::atomic_flag的原因是,该类在C++ 20标准前未提供的单独的测试函数testfinished.store(true, std::memory_order_release);while (!finished.load(std::memory_order_acquire))的内存顺序,实现了synchronizes-withhappens-before关系,保证在while (!finished.load(std::memory_order_acquire))成功时,instance.reset(new SingletonForMultithread);必定执行完毕,单例对象的创建是完整的。

完整的示例代码如下:

#include <atomic>
#include <cassert>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>using namespace std::chrono_literals;namespace {
constexpr size_t kThreadNum = 2000;
}class SingletonForMultithread {public:~SingletonForMultithread() = default;static SingletonForMultithread* GetInstance() {static std::unique_ptr<SingletonForMultithread> instance;static std::atomic_flag start_flag = ATOMIC_FLAG_INIT;static std::atomic<bool> finished(false);if (!start_flag.test_and_set(std::memory_order_relaxed)) {// The object created by the `new` operator may be relatively large and// time-consuming, therefore another atomic variable 'finished' is used to// ensure that other threads read a fully constructed singleton object. Do// not consider using another `std::atomic_flag`. Because it doesn't// provide a separate `test` function before the C++ 20 standard.instance.reset(new (std::nothrow) SingletonForMultithread);finished.store(true, std::memory_order_release);}// Wait in a loop until the singleton object is fully created, using// `std::this_thread::yield()` to save CPU resources.while (!finished.load(std::memory_order_acquire)) {std::this_thread::yield();}return instance.get();}private:SingletonForMultithread() {// Simulate a constructor that takes a relative long time.std::this_thread::sleep_for(10ms);}SingletonForMultithread(const SingletonForMultithread&) = delete;SingletonForMultithread& operator=(const SingletonForMultithread&) = delete;SingletonForMultithread(SingletonForMultithread&&) = delete;SingletonForMultithread& operator=(SingletonForMultithread&&) = delete;
};int main() {std::vector<std::thread> customers;for (size_t i = 0; i < kThreadNum; ++i) {customers.emplace_back(&SingletonForMultithread::GetInstance);}for (size_t i = 0; i < kThreadNum; ++i) {customers[i].join();}auto* singleton = SingletonForMultithread::GetInstance();assert(singleton != nullptr);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/19264.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新SDK平台下载开源全志D1-H/D1s的SDK

获取SDK SDK 使用 Repo 工具管理&#xff0c;拉取 SDK 需要配置安装 Repo 工具。 Repo is a tool built on top of Git. Repo helps manage many Git repositories, does the uploads to revision control systems, and automates parts of the development workflow. Repo is…

C语言的转义字符

转义字符也叫转移序列&#xff0c;包含如下&#xff1a; 转移序列 \0oo 和 \xhh 是 ASCII 码的特殊表示。 八进制数示例&#xff1a; 代码&#xff1a; #include<stdio.h> int main(void) {char beep\007;printf("%c\n",beep);return 0; }结果&#xff1a; …

机器视觉初步14:相机标定原理及应用

相机标定是指通过已知的相机参数&#xff0c;解算相机内部参数矩阵和外部参数矩阵。 文章目录 1.为什么要标定&#xff1f;2.工业场景中常见的标定方法2.1. 使用棋盘格标定板&#xff08;Checkerboard Markers&#xff09;2.2 使用相机自标定2.3. 使用三维物体标定2.4.九孔标…

【Linux】—— 进程的创建和退出

序言&#xff1a; 在上期&#xff0c;我们已经对 Linux的进程的相关知识进行了相关的学习。接下来&#xff0c;我们要学习的便是关于进程控制 的基本知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;进程创建 1、fork函数初识 2、写时拷贝 3、f…

Docker中容器数据卷

容器数据卷 一.什么是容器数据卷二.使用数据卷方式一&#xff1a;直接使用命令来挂载 -v 三.具名和匿名挂载 一.什么是容器数据卷 docker理念 将应用和环境打包成一个镜像&#xff01; 数据&#xff1f;如果数据都在容器中&#xff0c;那么我们的容器删除&#xff0c;数据就会…

oracle 19c打补丁遭遇OPATCHAUTO-72043OPATCHAUTO-68061

最近&#xff0c;在AIX上的新装oracle 19C数据库基础版本&#xff0c;使用opatchauto打PSU补丁集35037840时遇到了OPATCHAUTO-72043报错&#xff0c;无法正常应用GI补丁。 一、环境描述 操作系统&#xff1a;AIX 数据库版本&#xff1a;oracle rac 19.3.0新装基础版 应用PS…

2023拒绝内卷!两年转行网络安全真实看法!

我目前转行网络安全两年&#xff0c;没啥天分&#xff0c;全靠努力&#xff0c;基本能够得上中级的水平了。看到大家对转行网络安全挺感兴趣&#xff0c;也有挺多争议&#xff0c;想把我的建议和经验告诉大家。 有很多人觉得网络安全已经饱和了&#xff0c;现在选择这个工作&a…

Spring Boot : ORM 框架 JPA 与连接池 Hikari

数据库方面我们选用 Mysql &#xff0c; Spring Boot 提供了直接使用 JDBC 的方式连接数据库&#xff0c;毕竟使用 JDBC 并不是很方便&#xff0c;需要我们自己写更多的代码才能使用&#xff0c;一般而言在 Spring Boot 中我们常用的 ORM 框架有 JPA 和 Mybaties &#xff0c;本…

面试之多线程案例(四)

1.单例模式 单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时&#xff0c;为了防止频繁地创建对象使得内存飙升&#xff0c;单例模式可以让程序仅在内存中创建一个对象&#xff0c;让所有需要调用的地方都共享这一单例对象。…

[React]生命周期

前言 学习React&#xff0c;生命周期很重要&#xff0c;我们了解完生命周期的各个组件&#xff0c;对写高性能组件会有很大的帮助. Ract生命周期 React 生命周期分为三种状态 1. 初始化 2.更新 3.销毁 初始化 1、getDefaultProps() 设置默认的props&#xff0c;也可以用duf…

【数据结构】实现单链表的增删查

目录 1.定义接口2.无头单链表实现接口2.1 头插addFirst2.2 尾插add2.3 删除元素remove2.4 修改元素set2.5 获取元素get 3.带头单链表实现接口3.1 头插addFirst3.2 尾插add3.3 删除元素remove3.4 判断是否包含元素element 1.定义接口 public interface SeqList<E>{//默认…

关于拓扑排序

又重新学了一下拓扑排序&#xff0c;这次发现就十分简单了&#xff0c;拓扑排序的步骤 1.他必须是一个有向无环图&#xff0c;起点我们就是入度为0的点 2.我们首先要输出的就是入度为0的点&#xff0c;然后依次删除这些点连向的点&#xff0c;使这些点的入度-1&#xff0c;如果…

Java集合数组相互转换

1.集合转换成数组 &#xff08;1&#xff09;集合a通过toArray()方法进行转换为数组&#xff0c;可以转换成为指定类型的数组&#xff0c;但是这些类型都必须是object类型的子类&#xff0c;基本类型不可以。 必要时我们使用强制类型转换&#xff0c;转成我们需要的类型。 Li…

葡萄叶病害数据集

1.数据集 分成训练集和测试集 训练集有四个文件夹 第一个文件夹为 Grape Black Measles&#xff08;葡萄黑麻疹&#xff09;病害&#xff08;3783张&#xff09; Grape Black rot葡萄黑腐病病害数据集&#xff08;3596张&#xff09; Grape Healthy 健康葡萄叶&#xff08;25…

Maven项目解决cannot resolve plugin maven-deploy-plugin:2.7

导入maven项目后&#xff0c;编辑的时候提示一些插件加载失败&#xff01;大概率是你的网络有问题&#xff0c;插件下载失败。 如下图&#xff1a;&#xff08;网络突然好了&#xff0c;我想截图但是没有复现&#xff0c;用网上找到的截图代替&#xff0c;明白意思就行&#x…

Anaconda创建虚拟环境

参考文章 1 Win10RTX3060配置CUDA等深度学习环境 pytorch 管网&#xff1a;PyTorch 一 进入 Anaconda 二 创建虚拟环境 conda create -n pytorch python3.9注意要注意断 VPN切换镜像&#xff1a; 移除原来的镜像 # 查看当前配置 conda config --show channels conda config…

4-百度地图

4-百度地图 一 百度地图 1 前期准备 H5端和PC端,对接百度提供JavaScript API。 移动端,对接百度android SDK或ios SDK (1)打开百度地图开放平台 地址:https://lbsyun.baidu.com/ (2)选中开发文档——JavaScript Api 按照文档步骤开通百度开放平台并申请密钥 2 展示地…

Java多线程(四)

目录 一、线程的状态 1.1 观察线程的所有状态 1.2 线程状态和状态转移的意义 1.2.1 NEW、RUNNABLE、TERMINATED状态转换 1.2.2 WAITING、BLOCKED、TIMED_WAITING状态转换 1.2.3 yield()大公无私让出cpu 一、线程的状态 1.1 观察线程的所有状态 public class Demo9 {public st…

在linux中进行arm交叉编译体验tiny6410裸机程序开发流程

在某鱼上找了一个友善之臂的Tiny6410开发板用来体验一下嵌入式开发。这次先体验一下裸机程序的开发流程&#xff0c;由于这个开发板比较老旧了&#xff0c;官方文档有很多过期的内容&#xff0c;所以记录一下整个过程。 1. 交叉编译器安装 按照光盘A中的文档《04- Tiny6410 L…

快速开发框架若依的基础使用详解

Hi I’m Shendi 快速开发框架若依的基础使用详解 最近在为公司制作新的项目&#xff0c;经过了一段时间的技术沉淀&#xff0c;我开始尝试接触市面上用的比较多的快速开发框架&#xff0c;听的最多的当属若依吧 于是就选用了若依 介绍 为什么选&#xff1f;目的是为了提高开发…