单例模式和读者写者问题

文章目录

  • 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 锁,允许其他读者或写者访问数据。

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

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

相关文章

009——二叉树

目录 二叉树的五种基本形态&#xff1a; 1.二叉树可以是空树 2.只有一个根节点的树 3.斜树&#xff1a;只有左子树或右子树的树 4.左右孩子都有的树 二叉树的性质&#xff1a; 1.假设根节点是第一层&#xff0c;在二叉树的第i层上最多有2^(n-1)个结点 2.深度为k的二叉树…

WebGoat JAVA反序列化漏洞源码分析

目录 InsecureDeserializationTask.java 代码分析 反序列化漏洞知识补充 VulnerableTaskHolder类分析 poc 编写 WebGoat 靶场地址&#xff1a;GitHub - WebGoat/WebGoat: WebGoat is a deliberately insecure application 这里就不介绍怎么搭建了&#xff0c;可以参考其他…

Bash 脚本中解析 INI 文件

在 Bash 脚本中解析 INI 文件可能稍显复杂&#xff0c;因为 Bash 本身并未内置专门用于处理此类配置文件的工具。然而&#xff0c;我们可以通过一些文本处理命令&#xff08;例如 grep、sed、awk&#xff09;或借助外部工具&#xff08;如 python 脚本或 inih 库&#xff09;来…

基于SSM的旅游网站【附源码】

基于SSM的旅游网站&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概要设计 4.2 系统功能结构设计 4.3 数据库设计 4.3.1 数据库E-R图设计 4.3.2 数据库表结构设计 5 系统实现 5.1 管理员功能介绍 5.1.1 用户管理 5.1.2 …

每天五分钟深度学习pytorch:基于pytorch搭建多项式线性回归模型

本文重点 我们前面学习了基于pytoch搭建一元线性回归模型,本文我们学习使用pytorch搭建多项式的回归模型,在学习这个之前我们需要了解一下什么是多项式回归模型? 多项式回归模型和多变量(多特征)的回归模型是不一样的,多变量就是一个样本有多个特征属性,而多项式就是一…

云原生化 - 工具镜像(完整版)

在微服务和云原生环境中,容器化的目标之一是尽可能保持镜像小型化以提高启动速度和减少安全风险。然而,在实际操作中,有时候需要临时引入一些工具来进行调试、监控或问题排查。Kubernetes提供了临时容器(ephemeral containers)的功能,允许在不改变原始容器镜像的情况下,…

Centos7 搭建logstash

下载并安装公共签名密钥&#xff1a; sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch 创建一个名为 /etc/yum.repos.d/logstash.repo 的文件&#xff0c;并添加以下内容&#xff1a; [logstash-7.x] nameElastic repository for 7.x packages baseu…

【进阶】面向对象之权限修饰符代码块

文章目录 权限修饰符权限修饰符的使用规则 代码块分类局部代码块(了解就行)构造代码块(了解就行)静态代码块(重点) 权限修饰符 权限修饰符的使用规则 成员变量私有方法公开 特例&#xff1a; 如果方法中的代码是抽取其他方法中共性代码&#xff0c;这个方法一般也私有. 代码…

如何“半路出家”转行算法工程师的?

01 关于择业考虑 算法岗是什么&#xff1f; 算法岗&#xff0c;从根本内容上来说&#xff0c;是算法&#xff0c;算力&#xff0c;数据&#xff0c;应用场景的交集。从工作要求的角度来讲&#xff0c;是你的能力能够匹配大厂需要的工作要求。从个人角度来说&#xff0c;是你…

Gitea 忘记密码解决方案

一、在 Windows 上处理 Gitea 密码问题 1. 打开命令提示符&#xff1a; 以管理员身份运行命令提示符 2. 导航到 Gitea 安装目录&#xff1a; 假设你在 C:\gitea 下安装了 Gitea&#xff0c;使用以下命令导航到 Gitea 目录&#xff1a; cd C:\gitea 3. 重置密码&#xff1…

网易博客旧文----bacnet学习系列之三----读取属性程序的分析

bacnet学习系列之三----读取属性程序的分析 2014-01-21 16:29:42| 分类&#xff1a; BACnet | 标签&#xff1a; |举报 |字号大中小 订阅 先上一张自己画的流程图 bacnet学习系列之三----读取属性程序的分析 - xzhoumin - MMZHOU的博客 程序一开始就是分析命令行&#xff0c;…

hbuilderx+uniapp+Android健身房管理系统 微信小程序z488g

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 用户功能…

【React】如何对组件加载进行优化

1. 懒加载 (Lazy Loading) 使用 React.lazy() 和 Suspense 来实现懒加载&#xff08;按需加载&#xff09;组件。只有在需要时才加载对应组件&#xff0c;可以减小初始加载的体积。 const LazyComponent React.lazy(() > import(./LazyComponent));function App() {retur…

初级网络工程师之从入门到入狱(五)

本文是我在学习过程中记录学习的点点滴滴&#xff0c;目的是为了学完之后巩固一下顺便也和大家分享一下&#xff0c;日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、链路聚合1.1、手动进行链路聚合1.1.1、 拓扑图&#xff1a;1.1.2、 LSW11.1.3、 LSW2 1.2、…

RabbitMQ(学习前言)

目录 学习MQ之前有必要先去温故下微服务知识体系&#xff0c;以加深本章节的理解 一、微服务间的通讯方式 1. 基本介绍 2. 同步通讯 2.1. 什么是同步通讯 2.2. 同步通讯存在的问题 问题一&#xff1a;耦合度高 问题二&#xff1a;性能和吞吐能力下降 问题三&#xff1a…

SpringMVC源码-处理器适配器HandlerAdapter

因为定义controller的方式有三种&#xff0c;每种不同的方式调用的方法不同&#xff0c;尤其是注解修饰的 方法名是自定义的 因此需要通过适配器模式来调用方法执行 initStrategies进行适配器的初始化 处理器适配器一共有如下四种: org.springframework.web.servlet.Handl…

数据结构与算法——Java实现 32.堆

人的想法和感受是会随着时间的认知改变而改变&#xff0c; 原来你笃定不会变的事&#xff0c;也会在最后一刻变得释然 —— 24.10.10 堆 堆是基于二叉树实现的数据结构 大顶堆每个分支的上一个节点的权值要大于它的孩子节点 小顶堆每个分支的上一个节点的权值要小于它的孩子…

开源催生开源:Tesla 如何加速 AI 发展

特斯拉最近宣布开源其特斯拉以太网传输协议 &#xff08;TTPoE&#xff09;&#xff0c;这是一种尖端网络结构&#xff0c;专为 AI/ML 数据中心环境中的高速、低延迟数据传输而设计&#xff0c;从而掀起了波澜。此举反映了特斯拉利用开源战略加速全行业进步的更广泛历史&#x…

Spring Boot课程问答:技术难题轻松解决

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

Spring Security(常见过滤器介绍)

Spring Security是一个基于Spring框架的安全性解决方案&#xff0c;为Java应用程序提供了一套全面的安全解决方案。 一、概述 Spring Security是Spring采用AOP&#xff08;面向切面编程&#xff09;思想&#xff0c;基于servlet过滤器实现的安全框架。它致力于保护基于Spring的…