当前位置: 首页 > news >正文

单例模式和读者写者问题

文章目录

  • 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 单例模式的特点

单例模式的主要特点包括:

  1. 唯一性:确保一个类只有一个实例。
  2. 全局访问:提供一个全局访问点来获取这个唯一的实例。

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 概念

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

image-20241011150938421

  • 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)
  1. 读者加锁
    • 首先,读者尝试获取 rlock 锁,以安全地修改 reader_count 变量。
    • 获取 rlock 后,读者增加 reader_count 的值。
    • 如果这是第一个进入的读者(即 reader_count 从0变为1),则需要获取 wlock 锁,以阻止写者写入数据。这是因为一旦有读者在读取数据,写者就不应该修改数据,否则会影响读者读取的一致性。(读者优先!)
    • 完成 reader_count 的增加和可能的 wlock 获取后,读者释放 rlock 锁。
  2. 读者解锁
    • 读者完成读取操作后,再次获取 rlock 锁,以便安全地减少 reader_count 的值。
    • 如果这是最后一个离开的读者(即 reader_count 从1变为0),则需要释放 wlock 锁,允许写者进行写入操作。
    • 完成 reader_count 的减少和可能的 wlock 释放后,读者释放 rlock 锁。
  3. 写者加锁
    • 写者尝试获取 wlock 锁,以独占访问权进行写入操作。
    • 一旦获取 wlock 锁,写者可以安全地进行写入操作,因为此时没有读者在读取数据。
  4. 写者解锁
    • 写者完成写入操作后,释放 wlock 锁,允许其他读者或写者访问数据。

相关文章:

单例模式和读者写者问题

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

内网wordpress更换IP后无法访问的解决办法

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

Spring Boot课程答疑:技术难题一网打尽

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…...

云卷云舒【超级数据库】:算力网络时代的云原生数据库

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

电脑分盘分盘

方案一&#xff1a;使用磁盘管理工具扩展卷功能将未分配磁盘合并到C盘 按WinR输入diskmgmt.msc并按Enter键打开磁盘管理工具。在主界面中右键单击C盘驱动器并选择“扩展卷”&#xff0c;然后按照提示流程操作即可扩展C盘空间。 WinR diskmgmt.msc 注意&#xff1a;虽然系统内置…...

四元数基础知识

背景 四元数是方向的 4 元组表示形式&#xff0c;它比旋转矩阵更简洁。 四元数对于分析涉及三维旋转的情况非常有效。 四元数广泛用于机器人技术、量子力学、计算机视觉和 3D 动画。 您可以在 Wikipedia 上了解有关基本数学概念的更多信息。 您还可以观看由 3blue1brown 制…...

『网络游戏』进入游戏主城UI跳转主城【26】

首先在Unity客户端中创建一个空节点重命名为MainCityWnd 设置父物体为全局 创建空节点钉在左上角作为角色信息UI 在钉子下创建Image 创建脚本&#xff1a;MainCityWnd.cs 编写脚本&#xff1a;MainCityWnd.cs 挂载脚本 创建脚本&#xff1a;MainCitySys.cs 编写脚本&#xff1a…...

多点低压差分(M-LVDS)线路驱动器和接收器——MS2111

MS2111 是多点低压差分 (M-LVDS) 线路驱动器和接收器。经过 优化&#xff0c;可运行在高达 200Mbps 的信号速率下。所有部件均符合 M LVDS 标准 TIA / EIA-899 。该驱动器的输出支持负载低至 30Ω 的多 点总线。 MS2111 的接收器属于 Type-2 &#xff0c; 可在 -1…...

regexp_split_to_table的作用

regexp_split_to_table 是 PostgreSQL 中的一个函数&#xff0c;用于将一个字符串根据正则表达式进行分割&#xff0c;并将结果返回为一个表格&#xff08;每个分割后的部分作为一行&#xff09;。这个函数非常有用&#xff0c;特别是在处理复杂字符串时。 语法 regexp_split…...

【MATLAB】基于RSSI的蓝牙定位程序,4个锚点、二维平面

目录 ​编辑 商品描述 主要功能 技术细节 适用场景 下载链接 商品描述 这款基于接收信号强度指示&#xff08;RSSI&#xff09;原理的蓝牙定位程序&#xff0c;专为需要高效、可靠定位解决方案的开发者和研究人员设计。它能够在二维平面内&#xff0c;通过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 时&#xff0c;msg 变量将会获取到当时的 props.msg 值。解构操作仅仅是将当前值复制到 msg 变量中&#xff0c;它并不会建立响应式连接。因此&#xff0c;当 props.msg 发生变化时&#xff0c;解构出的 msg 变量仍保持其原始值&#xff0c;不会自…...

总结拓展十三:SAP系统采购订单关闭实例分享

1、案例分享 我们集团A基地和B基地存在外包加工业务。A基地向B基地外包采购了多起不同类型的物料&#xff0c;近期有部分外包采购暂停&#xff0c;需要采购关闭未完成交货的采购订单。采购在关闭时出现2类报错问题&#xff0c;向我们IT咨询解决方案。 1&#xff09;报错类型 …...

内嵌服务器Netty Http Server

内嵌式服务器不需要我们单独部署&#xff0c;列如SpringBoot默认内嵌服务器Tomcat,它运行在服务内部。使用Netty 编写一个 Http 服务器的程序&#xff0c;类似SpringMvc处理http请求那样。举例&#xff1a;xxl-job项目的核心包没有SpringMvc的Controller层&#xff0c;客户端却…...

Maven打包运行,引入三方jar及打包,不导入本地库的方法

Maven打包运行&#xff0c;引入三方jar及打包&#xff0c;不导入本地库的方法 maven、打包、springboot、jar、本地、引入背景 业务系统要对接某硬件&#xff0c;需要用到其三方jar&#xff0c;maven官方仓库没有这个&#xff0c;我也没有maven&#xff0c;又不想mvn install…...

02复写零

复写零 我们先进行异地复写&#xff1a;代码如下 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++过程

当然&#xff0c;可以为您简要概述GCC编译C程序的各个步骤及其对应的具体命令。 GCC编译C的四个主要步骤 预处理&#xff08;Preprocessing&#xff09;编译&#xff08;Compilation&#xff09;汇编&#xff08;Assembly&#xff09;链接&#xff08;Linking&#xff09; 1…...

互动式教育技术:Spring Boot师生共评作业管理系统

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

【云从】三、计算机网络基础

文章目录 1、网络2、网络通信2.1 IP地址2.2 子网掩码2.3 网关2.4 私有地址和公有地址2.5 NAT网络地址转换 3、网络架构及设备 1、网络 网络&#xff0c;即通过通信线路&#xff08;如光纤、网线&#xff09;和通信设备&#xff08;如路由器、光猫&#xff09;&#xff0c;将各…...

读书笔记《向上生长》关于记忆、链接的一些思考

摘录 NOTES 1.大脑非常低效&#xff0c;记性不好&#xff0c;还会自圆其说。很多成绩不好的学生绝大部分不记笔记&#xff0c;记了也不看。 2.注意力和肌肉一样&#xff0c;存在耗损&#xff0c;也可以通过锻炼来加强。 3. 所有知识必须链接到已经有的知识&#xff0c;到用的时…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...

Chrome 浏览器前端与客户端双向通信实战

Chrome 前端&#xff08;即页面 JS / Web UI&#xff09;与客户端&#xff08;C 后端&#xff09;的交互机制&#xff0c;是 Chromium 架构中非常核心的一环。下面我将按常见场景&#xff0c;从通道、流程、技术栈几个角度做一套完整的分析&#xff0c;特别适合你这种在分析和改…...