单例模式和读者写者问题

文章目录

  • 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;可以参考其他…

基于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 …

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

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

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

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

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

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

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

本文是我在学习过程中记录学习的点点滴滴&#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系统工作原理…

基于pytorch的手写数字识别-训练+使用

import pandas as pd import numpy as np import torch import matplotlib import matplotlib.pyplot as plt from torch.utils.data import TensorDataset, DataLoadermatplotlib.use(tkAgg)# 设置图形配置 config {"font.family": serif,"mathtext.fontset&q…

洗衣店订单管理:Spring Boot技术革新

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

pytest(六)——allure-pytest的基础使用

前言 一、allure-pytest的基础使用 二、需要掌握的allure特性 2.1 Allure报告结构 2.2 Environment 2.3 Categories 2.4 Flaky test 三、allure的特性&#xff0c;allure.step()、allure.attach的详细使用 3.1 allure.step 3.2 allure.attach&#xff08;挺有用的&a…

如何利用wsl-Ubuntu里conda用来给Windows的PyCharm开发

前提&#xff1a;咱们在wsl-Ubuntu上&#xff0c;有conda的虚拟环境 咱们直接打开PyCharm,打开Settings 更换Python Interpreter即可 当然一开始可能没有下面的选项&#xff0c;需要我们点击右边的Add Interpreter 这里选择wsl 点击next 将这两步进行修改 可以看出来&#xff0…

kubernetes中微服务部署

微服务 问&#xff1a;用控制器来完成集群的工作负载&#xff0c;那么应用如何暴漏出去&#xff1f; 答&#xff1a;需要通过微服务暴漏出去后才能被访问 Service 是一组提供相同服务的Pod对外开放的接口借助Service&#xff0c;应用可以实现服务发现和负载均衡Service 默认只…

智谱开放平台API调用解析

一、什么是智谱AI 智谱AI成立于2019年&#xff0c;由‌清华大学计算机系知识工程实验室的技术成果转化而来&#xff0c;是一家致力于人工智能技术研发和应用的公司。智谱致力于打造新一代认知智能大模型&#xff0c;专注于做大模型的中国创新。 二、智谱开放平台API调用 官方文…

【LeetCode】动态规划—673. 最长递增子序列的个数(附完整Python/C++代码)

动态规划—673. 最长递增子序列的个数 前言题目描述基本思路1. 问题定义2. 理解问题和递推关系3. 解决方法3.1 动态规划方法3.2 优化方法 4. 进一步优化5. 小总结 代码实现PythonPython3代码实现Python 代码解释 CC代码实现C 代码解释1. 初始化&#xff1a;2. 动态规划过程&…

FiBiNET模型实现推荐算法

1. 项目简介 A031-FiBiNET模型项目是一个基于深度学习的推荐系统算法实现&#xff0c;旨在提升推荐系统的性能和精度。该项目的背景源于当今互联网平台中&#xff0c;推荐算法在电商、社交、内容分发等领域的广泛应用。推荐系统通过分析用户的历史行为和兴趣偏好&#xff0c;预…