单例模式和读者写者问题
文章目录
- 10. 线程安全的单例模式
- 10.1 什么是设计模式
- 10.2 什么是单例模式
- 10.3 单例模式的特点
- 10.4 饿汉方式和懒汉方式
- 10.5 单例模式的线程池
- 11. STL和智能指针的线程安全 问题
- 11.1 STL中的容器是否是线程安全的?
- 11.2 智能指针是否是线程安全的?
- 12. 其他常见的各种锁
- 13. 读者写者问题
- 13.1 概念
- 13.2 读写锁接口
- 13.3 读者优先的伪代码
10. 线程安全的单例模式
10.1 什么是设计模式
设计模式(Design Pattern)是软件工程中的一种最佳实践,它是在特定场景下解决特定问题的成熟模板或方案。设计模式是面向对象软件开发过程中经过验证的经验和智慧的结晶,它们提供了一种通用的、可复用的解决方案来解决在软件设计中遇到的常见问题。
10.2 什么是单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,其核心目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问、节省系统资源、协调系统中的共享资源时非常有用。
10.3 单例模式的特点
单例模式的主要特点包括:
- 唯一性:确保一个类只有一个实例。
- 全局访问:提供一个全局访问点来获取这个唯一的实例。
10.4 饿汉方式和懒汉方式
饿汉方式(Eager Initialization)
饿汉方式是指在程序启动时就立即创建单例对象。这种方式的优点是简单、线程安全,因为对象的创建是在程序启动时完成的,不存在多线程同时访问的问题。缺点是如果单例对象的创建比较耗时或者占用资源较多,可能会影响程序的启动速度。
懒汉方式的单例模式实现如下:
class Singleton
{
public:static Singleton& getInstance() {return instance;}
private:static Singleton instance; // 静态成员变量,饿汉式,直接在类中创建实例Singleton() {} // 私有构造函数Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值操作
};// 在类外初始化静态成员变量
Singleton Singleton::instance;
懒汉方式(Lazy Initialization)
懒汉方式是指在第一次使用单例对象时才创建它。这种方式的优点是可以延迟对象的创建,从而加快程序的启动速度,并且只有在真正需要时才创建对象。缺点是如果多个线程同时访问单例对象,可能会存在线程安全问题,所以要加锁。
线程不安全的懒汉方式实现的单例模式
class Singleton
{
public:static Singleton* getInstance() {if (instance == nullptr) {instance = new Singleton();}return instance;}
private:static Singleton* instance; // 静态成员变量指针,懒汉式,延迟创建实例Singleton() {} // 私有构造函数Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值操作
};// 在类外初始化静态成员变量指针
Singleton* Singleton::instance = nullptr;
使用局部静态变量来实现线程安全的懒汉式单例,因为局部静态变量的初始化在C++ 11中是线程安全的。
class Singleton
{
public:static Singleton& getInstance() {static Singleton instance; // 局部静态变量,线程安全的懒汉式return instance;}
private:Singleton() {} // 私有构造函数Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值操作
};
getInstance
方法中的局部静态变量instance
只会在第一次调用getInstance
时被创建,之后的调用都会返回同一个实例,这种方式既实现了懒汉式的延迟加载,又保证了线程安全。
使用加锁的方式
class Singleton
{
public:static Singleton* getInstance() {if (instance == nullptr) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.pthread_mutex_lock(&mutex); // 使用互斥锁, 保证多线程情况下也只调用一次 new.if (instance == nullptr) instance = new Singleton();pthread_mutex_unlock(&mutex);}return instance;}
private:static Singleton* instance; // 静态成员变量指针,懒汉式,延迟创建实例static pthread_mutex_t mutex; // 锁Singleton() {} // 私有构造函数Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值操作
};// 在类外初始化静态成员变量指针
Singleton* Singleton::instance = nullptr;
pthread_mutex_t Singleton::mutex = PTHREAD_MUTEX_INITIALIZER;
10.5 单例模式的线程池
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#include <vector>
#include <queue>
using namespace std;
struct ThreadData
{pthread_t tid;string name;
};// T表示任务的类型
template<class T>
class ThreadPool
{
public:// ...static ThreadPool* GetInstance(){ if(tp == nullptr) {pthread_mutex_lock(&lock);if(tp == nullptr) tp = new ThreadPool<T>();pthread_mutex_unlock(&lock);}return tp;}
private:ThreadPool(size_t num = defaultNum) : _threads(num) {pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}ThreadPool(const ThreadPool& tp) = delete;const ThreadPool operator=(const ThreadPool& tp) = delete;vector<ThreadData> _threads;queue<T> _tasks; // 任务,这是临界资源pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool* tp;static pthread_mutex_t lock;
};
template<class T>
ThreadPool<T>* ThreadPool<T>::tp = nullptr;
template<class T>
pthread_mutex_t ThreadPool<T>::lock = PTHREAD_MUTEX_INITIALIZER;
11. STL和智能指针的线程安全 问题
11.1 STL中的容器是否是线程安全的?
原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.
11.2 智能指针是否是线程安全的?
对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.
12. 其他常见的各种锁
- 悲观锁(Pessimistic Locking):在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
- 乐观锁(Optimistic Locking):每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
- CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
- 自旋锁(Spinlock):当一个线程尝试获取一个已经被其他线程持有的锁时,该线程不会立即进入等待状态(即不会释放CPU),而是在原地“自旋”,也就是不停地进行忙等待(busy-waiting),直到获取到锁。
- 当一个线程尝试获取一个已经被占用的自旋锁时,它会在原地循环检查锁的状态,直到锁变为可用。
- 自旋锁不会使线程进入睡眠状态,因此它是一种非阻塞的同步机制。
- 由于线程不会进入睡眠状态,自旋锁避免了线程上下文切换的开销。
- 由于自旋锁会导致CPU资源的占用,因此它更适合于那些预计会很快释放的锁。如果锁的持有时间较长,自旋锁可能会导致CPU资源的浪费。
- 如果持有自旋锁的线程发生阻塞,那么等待该锁的线程可能会无限期地自旋下去,导致死锁。
- 之前使用的都属于悲观锁,是否采用自旋锁取决于线程在临界资源会待多长时间。
- 公平锁(Fair Lock)是一种锁机制,它确保了线程获取锁的顺序与它们请求锁的顺序相同。换句话说,公平锁保证了“先来先服务”(FIFO,First-In-First-Out)的原则,即最先请求锁的线程将最先获得该锁。
- 非公平锁(Non-Fair Lock)是一种锁机制,它不保证线程获取锁的顺序与它们请求锁的顺序相同。这意味着当一个线程尝试获取一个非公平锁时,它可能会与已经等待该锁的其他线程竞争,而不管这些线程等待了多久。
13. 读者写者问题
13.1 概念
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。
- 3种关系:
- 写者 vs 写者 (互斥)
- 读者 vs 写者 (互斥,同步)
- 读者 vs 读者 (共享关系)这是和生产消费者模型的区别
- 2个角色:读者和写者
- 1个交易场所:数据交换的地点
为什么读者写者问题中读者和读者关系是共享 而生产消费者模型中 消费者和消费者的关系是互斥呢?
因为读者并不会对数据做处理,只是对数据进行读操作。而消费者会对数据进行数据处理。
一般来说,读者多,写者少。所以概率上讲读者更容易竞争到锁,写者可能会出现饥饿问题。
这是读者写者问题的特点。也可以更改这个现象,设置同步策略,让写者优先
- 读者优先:在这种策略下,如果读者和写者同时等待访问临界区,读者会被优先允许进入。这种策略可以减少写者的等待时间,因为读者通常持有锁的时间较短。然而,如果读者持续不断地访问数据,写者可能会遭遇饥饿,即长时间无法获得对数据的访问权。
- 写者优先::在这种策略下,如果读者和写者同时等待访问临界区,写者会被优先允许进入。这种策略可以防止读者饥饿,因为写者一旦获得访问权,会阻止新的读者进入,直到写者完成写操作。但是,如果写者频繁地访问数据,读者可能会遭遇饥饿。
13.2 读写锁接口
// 初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t* restrict attr);
// 销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
// 加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
13.3 读者优先的伪代码
int reader_count = 0;
mutex_t rlock, wlock;// 读者加锁 && 解锁
lock(&rlock);
read_count++;
if(reader_count==1) lock(&wlock);
unlock(&rlock);
// 读者进行读取
lock(&rlock);
reader_count--;
if(reader_count==0) unlock(&wlock);
unlock(rlock);// 写者加锁 && 解锁
lock(&wlock);
// 写者进行写入操作
unlock(&wlock)
- 读者加锁:
- 首先,读者尝试获取
rlock
锁,以安全地修改reader_count
变量。 - 获取
rlock
后,读者增加reader_count
的值。 - 如果这是第一个进入的读者(即
reader_count
从0变为1),则需要获取wlock
锁,以阻止写者写入数据。这是因为一旦有读者在读取数据,写者就不应该修改数据,否则会影响读者读取的一致性。(读者优先!) - 完成
reader_count
的增加和可能的wlock
获取后,读者释放rlock
锁。
- 首先,读者尝试获取
- 读者解锁:
- 读者完成读取操作后,再次获取
rlock
锁,以便安全地减少reader_count
的值。 - 如果这是最后一个离开的读者(即
reader_count
从1变为0),则需要释放wlock
锁,允许写者进行写入操作。 - 完成
reader_count
的减少和可能的wlock
释放后,读者释放rlock
锁。
- 读者完成读取操作后,再次获取
- 写者加锁:
- 写者尝试获取
wlock
锁,以独占访问权进行写入操作。 - 一旦获取
wlock
锁,写者可以安全地进行写入操作,因为此时没有读者在读取数据。
- 写者尝试获取
- 写者解锁:
- 写者完成写入操作后,释放
wlock
锁,允许其他读者或写者访问数据。
- 写者完成写入操作后,释放
相关文章:

单例模式和读者写者问题
文章目录 10. 线程安全的单例模式10.1 什么是设计模式10.2 什么是单例模式10.3 单例模式的特点10.4 饿汉方式和懒汉方式10.5 单例模式的线程池 11. STL和智能指针的线程安全 问题11.1 STL中的容器是否是线程安全的?11.2 智能指针是否是线程安全的? 12. 其他常见的各种锁13. 读…...

内网wordpress更换IP后无法访问的解决办法
一、现象 一台装有wordpress的台式机,从一个校区移到了另一个校区,更换了IP地址,导致无法正常访问。 二、分析 安装wordpress的时候里面的ip(或域名)都已固定。安装好后,内网通过IP访问&am…...

Spring Boot课程答疑:技术难题一网打尽
4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式,是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示: 图4-1系统工作原理…...

云卷云舒【超级数据库】:算力网络时代的云原生数据库
一直关注算力网络,再次分析下移动云的数据库团队,他们在做的一些事情其实比较务实,在推进数据库依托云原生演进到算力网络阶段,这都是在构建一个能够承载无限容量、无感接入、多模融合、智能调度的超级数据库。 未来数据库&#…...

电脑分盘分盘
方案一:使用磁盘管理工具扩展卷功能将未分配磁盘合并到C盘 按WinR输入diskmgmt.msc并按Enter键打开磁盘管理工具。在主界面中右键单击C盘驱动器并选择“扩展卷”,然后按照提示流程操作即可扩展C盘空间。 WinR diskmgmt.msc 注意:虽然系统内置…...
四元数基础知识
背景 四元数是方向的 4 元组表示形式,它比旋转矩阵更简洁。 四元数对于分析涉及三维旋转的情况非常有效。 四元数广泛用于机器人技术、量子力学、计算机视觉和 3D 动画。 您可以在 Wikipedia 上了解有关基本数学概念的更多信息。 您还可以观看由 3blue1brown 制…...

『网络游戏』进入游戏主城UI跳转主城【26】
首先在Unity客户端中创建一个空节点重命名为MainCityWnd 设置父物体为全局 创建空节点钉在左上角作为角色信息UI 在钉子下创建Image 创建脚本:MainCityWnd.cs 编写脚本:MainCityWnd.cs 挂载脚本 创建脚本:MainCitySys.cs 编写脚本:…...

多点低压差分(M-LVDS)线路驱动器和接收器——MS2111
MS2111 是多点低压差分 (M-LVDS) 线路驱动器和接收器。经过 优化,可运行在高达 200Mbps 的信号速率下。所有部件均符合 M LVDS 标准 TIA / EIA-899 。该驱动器的输出支持负载低至 30Ω 的多 点总线。 MS2111 的接收器属于 Type-2 , 可在 -1…...
regexp_split_to_table的作用
regexp_split_to_table 是 PostgreSQL 中的一个函数,用于将一个字符串根据正则表达式进行分割,并将结果返回为一个表格(每个分割后的部分作为一行)。这个函数非常有用,特别是在处理复杂字符串时。 语法 regexp_split…...

【MATLAB】基于RSSI的蓝牙定位程序,4个锚点、二维平面
目录 编辑 商品描述 主要功能 技术细节 适用场景 下载链接 商品描述 这款基于接收信号强度指示(RSSI)原理的蓝牙定位程序,专为需要高效、可靠定位解决方案的开发者和研究人员设计。它能够在二维平面内,通过4个锚点实现对未…...
利用 langchain 和 LLM 来给 PDF 做总结
在网上看到一个PDF, 讲的是 Gstreamer 的的动态管道的构建, 一瞥而过, 没时间细看, 先写个小程序通过 langchain 和 LLM 给它做个快速总结 代码如下 from langchain.document_loaders import UnstructuredPDFLoader from langchain.llms import OpenAI from langchain.chains i…...
props 不能轻易解构,注意maxLength类似这种,不能解构出来
当您从 props 对象中解构 msg 时,msg 变量将会获取到当时的 props.msg 值。解构操作仅仅是将当前值复制到 msg 变量中,它并不会建立响应式连接。因此,当 props.msg 发生变化时,解构出的 msg 变量仍保持其原始值,不会自…...
总结拓展十三:SAP系统采购订单关闭实例分享
1、案例分享 我们集团A基地和B基地存在外包加工业务。A基地向B基地外包采购了多起不同类型的物料,近期有部分外包采购暂停,需要采购关闭未完成交货的采购订单。采购在关闭时出现2类报错问题,向我们IT咨询解决方案。 1)报错类型 …...

内嵌服务器Netty Http Server
内嵌式服务器不需要我们单独部署,列如SpringBoot默认内嵌服务器Tomcat,它运行在服务内部。使用Netty 编写一个 Http 服务器的程序,类似SpringMvc处理http请求那样。举例:xxl-job项目的核心包没有SpringMvc的Controller层,客户端却…...
Maven打包运行,引入三方jar及打包,不导入本地库的方法
Maven打包运行,引入三方jar及打包,不导入本地库的方法 maven、打包、springboot、jar、本地、引入背景 业务系统要对接某硬件,需要用到其三方jar,maven官方仓库没有这个,我也没有maven,又不想mvn install…...

02复写零
复写零 我们先进行异地复写:代码如下 public class Test {public static void main(String[] args) {int []array {1,0,2,3,0,4};duplicateZeros(array);}public static void duplicateZeros(int[] arr) {int [] elemnew int[arr.length];for(int cur0,dest0;des…...
01-gcc编译c++过程
当然,可以为您简要概述GCC编译C程序的各个步骤及其对应的具体命令。 GCC编译C的四个主要步骤 预处理(Preprocessing)编译(Compilation)汇编(Assembly)链接(Linking) 1…...

互动式教育技术:Spring Boot师生共评作业管理系统
3系统分析 3.1可行性分析 通过对本师生共评的作业管理系统实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本师生共评的作业管理系统采用JAVA作为开发语言&…...

【云从】三、计算机网络基础
文章目录 1、网络2、网络通信2.1 IP地址2.2 子网掩码2.3 网关2.4 私有地址和公有地址2.5 NAT网络地址转换 3、网络架构及设备 1、网络 网络,即通过通信线路(如光纤、网线)和通信设备(如路由器、光猫),将各…...
读书笔记《向上生长》关于记忆、链接的一些思考
摘录 NOTES 1.大脑非常低效,记性不好,还会自圆其说。很多成绩不好的学生绝大部分不记笔记,记了也不看。 2.注意力和肌肉一样,存在耗损,也可以通过锻炼来加强。 3. 所有知识必须链接到已经有的知识,到用的时…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...