Linux:多线程(单例模式,其他常见的锁,读者写者问题)

news2025/3/11 15:19:09

目录

单例模式

什么是设计模式

单例模式介绍

饿汉实现方式和懒汉实现方式

其他常见的各种锁

自旋锁

读者写者问题

逻辑过程

接口介绍


单例模式

什么是设计模式

设计模式就是一些大佬在编写代码的过程中,针对一些经典常见场景,给定对应解决方案,于是将其设计成一种模式,以后我们想使用就只需要套这个模式就好了。

单例模式介绍

某些类,只应该具有一个对象(实例化),称之为单例

在很多服务器开发场景中,经常需要让服务器加载很多数据到内存中,此时往往需要单例的类来管理这些数据。

饿汉实现方式和懒汉实现方式

static修饰的对象当类被加载到内存就被定义出来了,而不是等到类实例化对象后才定义。static修饰的成员在类中只有一份 饿汉和懒汉式通过static修饰的成员属于整个类,不管类实例化多少对象,都只有一个静态成员。

饿汉实现方式

饿汉实现方式就是:当类加载到到内存,就将成员变量定义出来了。

template<class T>
class Singleton{
    static T data;//类加载到内存直接定义
public:
    //获得data
    static T* GetInstance(){
        return &data;
    }
}

懒汉实现方式

懒汉实现方式就是,类加载时并没有将对象定义出来,而是在需要时才定义。

template<class T>
class Singleton(){
    //类加载时只是定义一个指针,
    static T* inst;
public:
    //当需要inst时,才会定义
    static T* GetInstance(){//不需要this指针,inst属于整个类。是静态成员
        if(inst == nullptr){
            inst = new T();
        }
        return inst;
    }
}

 饿汉实现方式会比懒汉实现方式启动得慢,因为饿汉实现方式在启动时就将需要的成员全部定义出来了。懒汉将定义时间挪后了。

 饿汉模式在使用时不会出现线程安全问题,但是在懒汉模式在使用时可能会出现线程安全问题。

 在多线程中,当两个线程同时调用GetInstance时,可能会创建出两个inst成员。 

懒汉模式线程安全版本

template<class T>
class Singleton(){
    //类加载时只是定义一个指针,
    volatile static T* inst;
    static Std::mutex lock;
public:
    //当需要inst时,才会定义
    static T* GetInstance(){//不需要this指针,inst属于整个类。是静态成员
        if(inst == nullptr){//双重判定,降低锁的冲突概率,提高性能
            lock.lock();    //加锁,保证只由一个线程可以进入
            if(inst == nullptr){
                inst = new T();
            }
            lock_unlock();
        } 
        return inst;
    }
}

注意事项:

  • 加锁位置,inst是临界资源,只需要对其加锁。

  • 两重判定。不一定每一个线程就来inst都为空,不至于每一个线程都加锁等待。

  • volatile,防止编译器优化。

其他常见的各种锁

悲观锁和乐观锁是两种并发控制的策略,而自旋锁、公平锁和非公平锁则属于具体实现并发控制的方式

1. 悲观锁(Pessimistic Locking)

  • 在每次对共享资源进行操作时都持有锁,认为其他线程会修改数据,因此在操作之前先加锁。

  • 主要用于保证并发环境下数据的一致性和可靠性。

  • 常见的悲观锁实现包括:互斥锁、读写锁等。

2. 乐观锁(Optimistic Locking)

  • 在操作共享资源时假设并发冲突的概率不高,因此不立即加锁,而是在更新时检查是否有其他线程修改过数据。

  • 乐观锁通常会使用版本号机制或CAS操作(Compare and Swap)来确保数据的一致性。\

  • CAS是一种乐观锁的实现方式,在更新数据时,会比较当前内存值和之前读取的值是否相等,如果相等说明数据未被修改,就可以进行更新操作,否则会失败。

  • CAS是一种原子操作,通常是一个自旋过程,即不断重试直到CAS成功或者达到重试次数。

  • 乐观锁避免了频繁加锁解锁的开销,适合读多写少的场景。

3. 自旋锁(Spin Lock)

  • 自旋锁是一种基于忙等待的锁,当线程尝试获取锁时如果锁已经被其他线程占用了,该线程会处于忙等待状态,直到锁被释放。

  • 自旋锁适用于短暂持有锁的情况,长时间持有锁会造成CPU资源的浪费。

4. 公平锁与非公平锁

  • 公平锁指的是对锁的获取按照请求的顺序进行,保证每个线程都有机会获取锁,即先到先得。

  • 非公平锁则允许锁的获取不按照请求顺序,有可能后到的线程会在先前请求而未获得锁的线程之前获取锁。

  • 非公平锁可以提高整体吞吐量,但可能导致优先级反转等问题。

自旋锁

自旋锁是一种基于忙等待的锁,当一个线程尝试获取自旋锁时,如果锁已经被其他线程占用,该线程会进行自旋操作,即不断检查锁的状态是否被释放,而不是立即被挂起等待。这种方式可以减少线程上下文切换的性能开销,适用于临界区内操作时间短暂的情况。

如何衡量临界区内操作时间:

  • 统计分析:通过在临界区内添加时间戳或者计时器,可以统计每个线程在临界区内的实际操作时间。这样可以得出平均操作时间、最大操作时间等数据。

  • 经验估计:根据对应用程序的了解和经验,估计临界区内操作的典型执行时间。这种方法可能不够精确,但可以作为初步评估。

  • 实际观察:观察程序的实际运行情况,包括临界区内操作的执行时间和频率。根据观察结果来评估操作的时间。

还是看我们的经验来选择合适,恰当的锁

1. 初始化自旋锁

int pthread_spin_init(pthread_spinlock_t* lock, int shared);
  • 功能:初始化互斥锁。

  • 参数pthread_spinlock_t* lock表示需要被初始化的自旋锁的地址。int shared表示锁的是否进程间共享,0表示共享,非0表示不共享,一般都设为0。

  • 返回值:取消成功返回0,取消失败返回错误码。

2. 销毁自旋锁

int pthread_spin_destroy(pthread_spinlock_t* lock);
  • 功能:销毁互斥锁。

  • 参数pthread_spinlock_t* lock表示需要被销毁的自旋锁的地址。

  • 返回值:销毁成功返回0,失败返回-1。

3. 添加自旋锁

int pthread_spin_lock(pthread_spinlock_t* lock);
  • 功能:对lockunlock的部分代码加锁(仅允许线程串行)。

  • 参数pthread_spinlock_t* lock表示需要加锁的锁指针。

  • 返回值:加锁成功返回0,失败返回-1。

4. 释放自旋锁

int pthread_spin_unlock(pthread_spinlock_t* lock);
  • 功能:标识走出lockunlock的部分代码解锁(恢复并发)。

  • 参数pthread_spinlock_t* lock表示需要解锁的锁指针。

  • 返回值:解锁成功返回0,失败返回-1。

读者写者问题

在多线程编程中,有时候会遇到一种常见的情况,即某些共享数据的修改操作相对较少,而读取操作却非常频繁,且读取操作中可能会伴随着耗时较长的查找操作。在这种情况下,如果对整个数据结构进行加锁,那么即使是读取操作也需要等待锁的释放,这会导致程序效率降低。

为了解决这种情况,可以使用读写锁。读写锁允许多个线程同时获取读锁,只有在获取写锁时才会阻塞其他线程。这样一来,在多读少写的情况下,多个线程可以同时获得读锁,从而提高了程序的并发性能,避免了不必要的阻塞。

总结一下,读写锁适用于多读少写的场景,可以通过允许多个线程同时获取读锁来提高程序的并发性能,避免不必要的阻塞,从而提高了程序的效率。

读者写者模型是用于描述多线程对共享数据进行读写操作时的一种经典并发模型。在读者写者模型中,有两类线程:读者和写者。读者线程只对共享数据进行读操作,而写者线程则对共享数据进行写操作。读者在读操作时不会互斥,多个读者可以同时访问共享数据(不会对数据进行修改),但写者在写操作时需要互斥,同时只允许一个写者访问共享数据且不允许其他任何读者或写者访问。

读者写者模型的目标是实现对共享数据的高效访问,保证数据的一致性和并发性。为了实现这一目标,通常会使用锁和条件变量等同步机制来控制读者和写者线程的访问。

  1. 1个交易场所

  2. 2个角色:读者与写者

  3. 3种关系:写者之间的互斥、读者之间没有关系、读者与写者之间的互斥与同步

    读者和写者之间保持互斥与同步意味着在读者写者模型中,确保读者和写者之间的操作互斥(不能同时访问共享数据)并且同步(按照一定规则进行访问)。具体来说:

  • 互斥(Mutual Exclusion):读者写者模型要求在写者对共享数据进行操作时,必须排他性地拥有对该数据的访问权,即其他任何读者或写者都不可以同时访问共享数据。这样做是为了避免数据一致性问题和争用条件(Race Condition)的发生,确保在写操作时数据不会同时被其他线程读或写。

  • 同步(Synchronization):读者写者模型还要求在读者和写者之间进行协调,保证数据的访问顺序和一致性。通常情况下,写者优先的规则要求在写者请求访问共享数据时,必须等待所有正在读取数据的读者完成操作后才能进行写入;而在有写者等待访问共享数据时,所有新的读者请求必须等待,直到写者完成操作。这种同步行为保证了数据的一致性和安全性。

逻辑过程
int reader_count = 0;
pthread_rwlock_t wlock;
pthread_rwlock_t rlock;

// 读者线程
void reader() {
    lock(&rlock); // 获取读者锁
    if (reader_count == 0) {
        lock(&wlock); // 如果当前没有读者,则获取写者锁
    }
    ++reader_count; // 增加读者计数
    unlock(&rlock); // 释放读者锁

    // 这里进行读取操作

    lock(&rlock); // 重新获取读者锁
    --reader_count; // 减少读者计数
    if (reader_count == 0) {
        unlock(&wlock); // 如果已经没有读者,释放写者锁
    }
    unlock(&rlock); // 释放读者锁
}

// 写者线程
void writer() {
    lock(&wlock); // 获取写者锁

    // 这里进行写入操作

    unlock(&wlock); // 释放写者锁
}

在上述伪代码中,我们模拟了读者写者模型的加锁逻辑,主要包括了对读者和写者线程进行互斥和同步控制。下面我们简要解释一下这段伪代码的逻辑:

  • reader_count表示当前正在读取数据的读者数量。

  • pthread_mutex_t wlockpthread_mutex_t rlock分别表示写者锁和读者锁,用于读者写者线程的互斥操作。

对于读者线程:

1. 首先获取读者锁rlock,确保读者线程之间的互斥。

2. 如果当前没有其他读者在读取数据,则获取写者锁wlock,确保写者无法进入。

  • 申请成功:就接着行下进行

  • 申请失败:说明写者正在写,那就阻塞等着

3. 增加reader_count计数器,表明有一个读者正在读取数据。

4. 释放读者锁,允许其他读者进入读取数据。

5. 进行读取操作。

当没有读者在读时,我们就会释放写者锁

对于写者线程:

  • 获取写者锁wlock,确保写者线程独占对共享数据的访问。

  • 进行写操作。

  • 释放写者锁,允许其他写者或读者访问数据。

接口介绍

1. 初始化读写锁

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
  • 功能:该函数用于初始化一个读写锁对象rwlock,可以指定属性attr,一般情况下可以传入NULL使用默认属性。

  • 参数rwlock:指向读写锁对象的指针,attr:读写锁的属性对象指针,可以为 NULL,表示使用默认属性。

  • 返回值:如果函数调用成功,返回值为 0;否则返回一个非零的错误码。

  • 说明:该函数用于初始化一个读写锁对象,可以指定一些属性,如锁的类型、优先级规则等。

2. 销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
  • 功能:用于销毁已经初始化的读写锁对象rwlock销毁读写锁后,该读写锁对象不可再使用,需要重新进行初始化。

  • 参数rwlock:指向读写锁对象的指针。

  • 返回值:如果函数调用成功,返回值为 0;否则返回一个非零的错误码。

  • 说明:该函数用于销毁已经初始化的读写锁对象,释放相关资源。

3. 获取读锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
  • 功能:该函数用于获取读锁,即允许多个线程同时获取读取权限,但在写锁被获取时将会阻塞。当读线程数较多时,考虑性能可以使用读锁。

  • 参数rwlock:指向读写锁对象的指针。

  • 返回值:如果函数调用成功,返回值为 0;否则返回一个非零的错误码。

  • 说明:该函数用于获取读锁,允许多个线程同时获取读取权限,但在写锁被获取时将会阻塞。

4. 获取写锁

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
  • 功能:该函数用于获取写锁,即独占地写入数据。一旦有线程获取了写锁,其他线程无法获取读锁或写锁,只能等待写锁的释放。

  • 参数rwlock:指向读写锁对象的指针。

  • 返回值:如果函数调用成功,返回值为 0;否则返回一个非零的错误码。

  • 说明:该函数用于获取写锁,独占地写入数据。一旦有线程获取了写锁,其他线程无法获取读锁或写锁,只能等待写锁的释放。

5. 释放锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
  • 功能:用于释放读锁或写锁,让其他线程可以获取读写锁。

  • 参数rwlock:指向读写锁对象的指针。

  • 返回值:如果函数调用成功,返回值为 0;否则返回一个非零的错误码。

  • 说明:该函数用于释放读锁或写锁,让其他线程可以获取读写锁,从而读取或写入共享数据。

我们对于读者里面的加锁就直接使用pthread_rwlock_rdlock,相当于上面的全部过程了

同理:对于写者里面的加锁就直接使用pthread_rwlock_wrlock

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

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

相关文章

【氮化镓】高输入功率应力诱导的GaN 在下的退化LNA退化

2019年,中国工程物理研究院电子工程研究所的Tong等人基于实验与第一性原理计算方法,研究了Ka波段GaN低噪声放大器(LNA)在高输入功率应力下的退化机制。实验结果表明,在27 GHz下施加1 W连续波(CW)输入功率应力后,LNA的增益下降约1 dB,噪声系数(NF)增加约0.7 dB。进一…

Javaweb后端文件上传@value注解

文件本地存储磁盘 阿里云oss准备工作 阿里云oss入门程序 要重启一下idea&#xff0c;上面有cmd 阿里云oss案例集成 优化 用spring中的value注解

git规范提交之commitizen conventional-changelog-cli 安装

一、引言 使用规范的提交信息可以让项目更加模块化、易于维护和理解&#xff0c;同时也便于自动化工具&#xff08;如发布工具或 Changelog 生成器&#xff09;解析和处理提交记录。 通过编写符合规范的提交消息&#xff0c;可以让团队和协作者更好地理解项目的变更历史和版本…

Java/Kotlin逆向基础与Smali语法精解

1. 法律警示与道德边界 1.1 司法判例深度剖析 案例一&#xff1a;2021年某游戏外挂团伙刑事案 犯罪手法&#xff1a;逆向《王者荣耀》通信协议&#xff0c;修改战斗数据包 技术细节&#xff1a;Hook libil2cpp.so的SendPacket函数 量刑依据&#xff1a;非法经营罪&#xff…

非软件开发项目快速上手:14款管理软件精选

文章介绍了以下14款项目管理系统&#xff1a;1.Worktile&#xff1b;2.Teambition&#xff1b;3.Microsoft Project&#xff1b;4.Forbes&#xff1b;5.WorkOtter&#xff1b;6.Trello&#xff1b;7.Smartsheet&#xff1b;8.Taiga&#xff1b;9.ClickUp&#xff1b;10.Monday.…

夸父工具箱(安卓版) 手机超强工具箱

如今&#xff0c;人们的互联网活动日益频繁&#xff0c;导致手机内存即便频繁清理&#xff0c;也会莫名其妙地迅速填满&#xff0c;许多无用的垃圾信息悄然占据空间。那么&#xff0c;如何有效应对这一难题呢&#xff1f;答案就是今天新推出的这款工具软件&#xff0c;它能从根…

混元图生视频-腾讯混元开源的图生视频模型

混元图生视频是什么 混元图生视频是腾讯混元推出的开源图生视频模型&#xff0c;用户可以通过上传一张图片进行简短描述&#xff0c;让图片动起来生成5秒的短视频。模型支持对口型、动作驱动和背景音效自动生成等功能。模型适用于写实、动漫和CGI等多种角色和场景&#xff0c;…

Debian系统grub新增启动项

参考链接 给grub添加自定义启动项_linux grub定制 启动项名称自定义-CSDN博客 www.cnblogs.com 1. boot里面的grub.cfg 使用vim打开boot里面的grub.cfg sudo vim /boot/grub/grub.cfg 这时候会看到文件最上方的提示 2. 真正配置grub的文件 从刚才看到的文件提示中&#x…

VSCode快捷键整理

VSCode快捷键整理 文章目录 VSCode快捷键整理1-VSCode 常用快捷键1-界面操作2-单词移动3-删除操作4-编程相关5-多光标操作6-文件、符号、函数跳转7-鼠标操作8-自动补全操作9-代码折叠操作 1-VSCode 常用快捷键 1-界面操作 文件资源管理器&#xff1a;Ctrl Shift E 跨文件搜…

刘火良 FreeRTOS内核实现与应用之1——列表学习

重要数据 节点的命名都以_ITEM后缀进行&#xff0c;链表取消了后缀&#xff0c;直接LIST 普通的节点数据类型 /* 节点结构体定义 */ struct xLIST_ITEM { TickType_t xItemValue; /* 辅助值&#xff0c;用于帮助节点做顺序排列 */ struct xLIST_I…

本地部署Navidrome个人云音乐平台随时随地畅听本地音乐文件

文章目录 前言1. 安装Docker2. 创建并启动Navidrome容器3. 公网远程访问本地Navidrome3.1 内网穿透工具安装3.2 创建远程连接公网地址3.3 使用固定公网地址远程访问 前言 今天我要给大家安利一个超酷的私有化音乐神器——Navidrome&#xff01;它不仅让你随时随地畅享本地音乐…

数据集构建与训练前准备

训练数据集目录结构与格式 作者笨蛋学法&#xff0c;先将其公式化&#xff0c;后面逐步自己进行修改&#xff0c;读者觉得看不懂可以理解成&#xff0c;由结果去推过程&#xff0c;下面的这个yaml文件就是结果&#xff0c;我们去推需要的文件夹(名字可以不固定&#xff0c;但是…

jenkins+ant+jmeter生成的测试报告空白

Jenkins能正常构建成功&#xff0c;但是打开Jenkins上的测试报告&#xff0c;则显示空白 在网上找了很多文章&#xff0c;结果跟别人对比测试报告的配置&#xff0c;发现自己跟别人写的不一样 所以跟着别人改&#xff0c;改成一样的再试试 结果&#xff0c;好家伙&#xff0…

利用阿里云Atlas地区选择器与Plotly.js实现数据可视化与交互

在数据科学与可视化领域&#xff0c;交互式图表和地图应用越来越成为数据分析和展示的重要手段。本文将介绍如何结合阿里云Atlas地区选择器与Plotly.js&#xff0c;创建动态交互式的数据可视化应用。 一、阿里云Atlas地区选择器简介 阿里云Atlas是阿里云的一款数据可视化产品…

linux安装java8 sdk,使用 tar.gz安装包手动安装

1. 下载 Java 8 SDK 首先&#xff0c;需要从 Oracle 的官方网站或 OpenJDK 的网站下载 Java 8 的 .tar.gz 文件。并上传到服务器 2. 解压 JDK 下载完成后&#xff0c;使用 tar 命令解压文件。打开服务器终端&#xff0c;然后使用以下命令&#xff1a; tar -xvzf jdk-8uXXX-…

6.聊天室环境安装 - Ubuntu22.04 - elasticsearch(es)的安装和使用

目录 介绍安装安装kibana安装ES客户端使用 介绍 Elasticsearch&#xff0c; 简称 ES&#xff0c;它是个开源分布式搜索引擎&#xff0c;它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自动发现&#xff0c;索引自动分片&#xff0c;索引副本机制&#xff0c;res…

【python爬虫】酷狗音乐爬取练习

注意&#xff1a;本次爬取的音乐仅有1分钟试听&#xff0c;仅作学习爬虫的原理&#xff0c;完整音乐需要自行下载客户端。 一、 初步分析 登陆酷狗音乐后随机选取一首歌&#xff0c;在请求里发现一段mp3文件&#xff0c;复制网址&#xff0c;确实是我们需要的url。 复制音频的…

计算机视觉cv2入门之图像空域滤波(待补充)

空域滤波 空域滤波是指利用像素及像素领域组成的空间进行图像增强的方法。这里之所以用滤波这个词,是因为借助了频域里的概念。事实上空域滤波技术的效果与频域滤波技术的效果可以是等价的&#xff0c;而且有些原理和方法也常借助频域概念来解释。 原理和分类 空域滤波是在图…

游戏引擎学习第149天

今日回顾与计划 在今天的直播中&#xff0c;我们将继续进行游戏的开发工作&#xff0c;目标是完成资产文件&#xff08;pack file&#xff09;的测试版本。目前&#xff0c;游戏的资源&#xff08;如位图和声音文件&#xff09;是直接从磁盘加载的&#xff0c;而我们正在将其转…

PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程(通用)!

PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程&#xff08;通用&#xff09;&#xff01; 当我们成功接入大模型时&#xff0c;可以选中任意代码区域进行解答&#xff0c;共分为三个区域&#xff0c;分别是选中区域、提问区域以及回答区域&#xff0c;我…