多线程杂谈:惊群现象、CAS、安全的单例

引言

本文是一篇杂谈,帮助大家了解多线程可能会出现的面试题。

目录

引言

惊群现象

结合条件变量

CAS原子操作(cmp & swap)

线程控制:两个线程交替打印奇偶数

智能指针线程安全

单例模式线程安全

最简单的单例(懒汉)模式


惊群现象

惊群效应(Thundering Herd Effect)是一个在计算机科学和网络领域中常见的现象,特别是在并发编程和分布式系统中。这个效应描述的是当多个进程或者线程几乎同时被唤醒或激活去处理一个任务或事件,但实际上只需要其中的一部分进程或线程来处理,导致资源的浪费和性能的下降。

下面详细解释一下惊群效应的几个关键点:

### 发生场景

1. **网络服务中的请求处理**:在处理网络请求时,如果有大量的请求同时到达,系统可能会唤醒所有的处理线程,但实际上只需要少数线程就能处理这些请求。

2. **锁竞争**:在多线程编程中,当多个线程试图获取同一个锁时,一旦锁被释放,所有的等待线程都可能被唤醒,但只有一个线程能够获得锁,其他线程将继续等待。

3. **事件驱动系统**:在事件驱动的系统中,一个事件可能会使得多个处理者被唤醒,但实际上只需一个处理者处理该事件。

### 原因

1. **同步机制**:系统中的同步机制(如信号量、锁等)可能会唤醒所有等待的进程或线程。

2. **缺乏精细的调度**:调度器没有足够的信息来决定应该唤醒哪些进程或线程,因此默认唤醒所有等待者。

### 影响

1. **性能下降**:不必要的进程或线程唤醒会导致上下文切换,增加CPU的负载,降低系统的响应速度和吞吐量。

2. **资源浪费**:唤醒过多的进程或线程会占用内存和其他系统资源,而这些资源实际上并不需要立即使用。

### 解决方案

1. **使用更精细的锁**:比如读写锁,可以允许多个读操作同时进行,而写操作则互斥。

2. **改进调度算法**:调度器可以根据特定的策略只唤醒必要的进程或线程。

3. **领导者选举**:在处理事件时,可以先选举一个领导者来处理事件,其他线程保持睡眠状态。

4. **使用消息队列**:通过消息队列来分配任务,只有当任务到达时才唤醒处理线程。

惊群效应是系统设计时需要考虑的一个重要问题,通过合理的设计和优化,可以有效地避免或减轻这一效应带来的负面影响。

根据之前提到的惊群效应及其影响,以下是一些具体的解决方案:

使用单线程或有限线程模型:

工作者线程(Worker Threads)模式:预先创建一定数量的工作者线程,每个线程从任务队列中获取并处理任务,避免同时唤醒过多线程。

线程池:通过线程池管理线程,可以限制同时运行的线程数量,避免创建过多的线程。

改进锁机制:

条件变量:使用条件变量来唤醒特定的线程,而不是所有等待的线程。

读写锁(Reader-Writer Lock):允许多个读操作同时进行,而写操作则互斥,减少锁竞争。

领导选举机制:

在处理特定任务时,通过选举机制选择一个线程作为领导者来处理任务,其他线程进入等待状态。

事件通知机制:

使用事件通知而不是轮询,只有当特定事件发生时才唤醒相关的线程。

消息队列:

通过消息队列来分配任务,线程可以根据队列中的消息来决定是否需要处理任务,减少不必要的唤醒。

负载均衡:

在分布式系统中,通过负载均衡技术将任务均匀分配到不同的服务器或线程上,避免某些节点过载。

细粒度锁:

使用细粒度锁代替粗粒度锁,减少锁的竞争范围,从而减少同时唤醒的线程数量。

限流和背压机制:

在系统层面实施限流策略,当系统负载过高时,通过背压机制告知上游减少请求发送,避免系统过载。

异步处理:

采用异步编程模型,任务提交后立即返回,实际处理在后台异步进行,减少线程等待和上下文切换。

通过上述解决方案,可以有效地减少惊群效应的发生,提高系统的性能和资源利用率。

结合条件变量

2. **锁竞争**:在多线程编程中,当多个线程试图获取同一个锁时,一旦锁被释放,所有的等待线程都可能被唤醒,但只有一个线程能够获得锁,其他线程将继续等待。

为什么把这一条添加进去呢?

这就要牵扯到wait与唤醒机制了。

唤醒时,一旦错误唤醒,就会出现恶性竞争。

CAS原子操作(cmp & swap)

整个处理流程中,假设内存中存在一个变量i,它在内存中对应的值是A(第一次读取),此时经过业务处理之后,要把它更新成B,那么在更新之前会再读取一下i现在的值C,如果在业务处理的过程中i的值并没有发生变化,也就是A和C相同,才会把i更新(交换)为新值B。如果A和C不相同,那说明在业务计算时,i的值发生了变化,则不更新(交换)成B。最后,CPU会将旧的数值返回。而上述的一系列操作由CPU指令来保证是原子的(来自程序新视界)。

在《Java并发编程实践》中对CAS进行了更加通俗的描述:我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少。

线程控制:两个线程交替打印奇偶数


// t1打印奇数
// t2打印偶数
// 交替打印
int main()
{mutex mtx;int x = 1;condition_variable cv;bool flag = false;// 如果保证t1先运行 condition_variable+flag// 交替运行thread t1([&]() {for (size_t i = 0; i < 10; i++){unique_lock<mutex> lock(mtx);if (flag)cv.wait(lock);cout << this_thread::get_id() << ":" << x << endl;++x;flag = true;cv.notify_one(); // t1notify_one的时候 t2还没有wait}});thread t2([&]() {for (size_t i = 0; i < 10; i++){unique_lock<mutex> lock(mtx);if (!flag)cv.wait(lock);cout << this_thread::get_id() << ":" << x << endl;++x;flag = false;cv.notify_one();}});t1.join();t2.join();return 0;
}

这是一道面试题,要求两个线程按顺序打印。

怎么保证线程一先运行呢?条件变量 + flag。(第一次不让线程一wait,而是线程2wait)

第一次的notify_one不起作用。

智能指针线程安全

template<class T>class shared_ptr{public:// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new atomic<int>(1)){}template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new atomic<int>(1)), _del(del){}// function<void(T*)> _del;void release(){if (--(*_pcount) == 0){//cout << "delete->" << _ptr << endl;//delete _ptr;_del(_ptr);delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}// sp1 = sp3shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}private:T* _ptr;//int* _pcount;atomic<int>* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };};

智能指针在拷贝构造的时候,内部的计数器++。万一两个线程都对智能指针调用拷贝构造,那么计数器就会错乱。

我们可以:1.上锁 2.atomic保护智能指针。

在目前的C++中,更新了如下的关于智能指针的安全性:

  • 引用计数的线程安全性std::shared_ptr对其内部的引用计数的操作(增加或减少)是线程安全的。这意味着多个线程可以安全地共享和复制同一个 std::shared_ptr 实例,而无需额外的同步机制。例如,在不同线程中拷贝同一个 std::shared_ptr 实例不会导致数据竞争。

  • 对象内容的线程安全性std::shared_ptr 不会对其管理的对象的内容进行任何保护,如果多个线程同时读写由 std::shared_ptr 管理的对象,那么就需要手动确保对该对象的访问是线程安全的。--只是提供RAII封装

  • 实例本身的线程安全性:对同一个 std::shared_ptr 实例的读写操作(例如,赋值和重置)是不安全的,需要额外的同步。

注意:shared_ptr(这个指针类)本身是线程安全的,但是他RAII指向的资源操作的时候不能保证线程安全。

我们可以理解为访问shared_ptr这个“壳子”的时候,是线程安全的,但是对“壳子”包含的对象不安全。

单例模式线程安全

懒汉模式的线程安全:由于即用即取,万一两个线程并发进行懒汉申请,那么就会出现线程安全,加锁就可以。

//2、提供获取单例对象的接口函数
static Singleton& GetInstance(){
if(_pslnst==nullptr)
{
//t1 t2
unique_lock<mutex>lock(_mtX);
if(_psinst==nullptr)
{
//第一次调用Getlnstance的时候创建单例对象
_psinst=newSingleton;
}
}
return*_psinst;
}

当后续存在单例之后,就需要重复的申请锁,减少了资源消耗。

同时双重判断也提供了保险机制。

最简单的单例(懒汉)模式

//懒汉
class Singleton {
public:// 2、提供获取单例对象的接口函数static Singleton* GetInstance() {// 局部的静态对象,是在第一次调用时初始化static Singleton inst;return &inst;}
private:// 1、构造函数私有Singleton() {cout << "Singleton()" << endl;}
};

私有构造--静态方法获取static实例。

注意:这是线程安全的!--静态局部对象只初始化一次!

局部的静态对象,是在第一次调用时初始化

C++11之前,他不是,也就说,C++11之前的编译器,那么这个代码不安全的

C++11之后可以保证局部静态对象的初始化是线程安全的,只初始化一次(不会获得两个实例)

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

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

相关文章

三分钟简单了解HTML的一些语句

1.图片建议建立一个文件夹如下图所示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"keywords"><title>魔神羽落</title><style>.testone{background-color: #ff53e…

HCIP笔记4--OSPF域内路由计算

1. 域内LSA 1.1 一类LSA 一类LSA: 路由器直连状态&#xff0c;Router LSA。 串口需要两端配置好IP,才会产生一类LSA; 以太网口只需要一端配置了IP就会直接产生一类LSA。 LSA通用头部 Type: Router 直连路由LS id: 12.1.1.1 路由器router idAdv rtr: 12.1.1.1 通告的路由器&…

k8s基础(7)—Kubernetes-Secret

Secret概述&#xff1a; Secret 是一种包含少量敏感信息例如密码、令牌或密钥的对象。 这样的信息可能会被放在 Pod 规约中或者镜像中。 使用 Secret 意味着你不需要在应用程序代码中包含机密数据。 由于创建 Secret 可以独立于使用它们的 Pod&#xff0c; 因此在创建、查看和…

【leetcode100】验证二叉搜索树

1、题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 1&…

谈谈MySQL中的索引和事务

目录 1. 索引 1.1 索引介绍 1.2 缺陷 1.3 使用 1.3.1 查看索引 1.3.2 创建索引 1.3.3 删除索引 2. 索引底层的数据结构 2.1 B树 3. 事务 3.1 为什么使用事务 3.2 事务的使用 3.3 事务的基本特性 1. 索引 1.1 索引介绍 索引相当于一本书的目录(index), 在一…

2024:CSDN上的收获与蜕变——我的技术成长之旅

2024&#xff1a;CSDN上的收获与蜕变——我的技术成长之旅 前言数据见证&#xff1a;2024年的创作足迹荣誉殿堂&#xff1a;各平台的创作证书与认可社区共建&#xff1a;行业贡献与互动交流展望未来&#xff1a;2025年的目标与计划结语 前言 博主简介&#xff1a;江湖有缘 在技…

博客之星2024年度-技术总结:技术探险家小板的一年的征程

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 技术探险家的新一年征程 2.0 数据库管理与优化&#xff1a;MySQL 的魔法森林 2.1 穿越基础概念的迷雾 2.2 实践应用&#xff1a;成为森林的主人 2.3 性能调优&…

【vim】vim怎样直接跳转到某行?

vim怎样直接跳转到某行&#xff1f; 一、使用行号跳转二、使用相对行号跳转三、使用标记跳转 在Vim中直接跳转到某行可以使用以下几种方法&#xff1a; 一、使用行号跳转 在命令模式下&#xff0c;输入冒号:&#xff0c;然后输入你想要跳转的行号&#xff0c;最后按回车键。例…

SentencePiece和 WordPiece tokenization 的含义和区别

SentencePiece和 WordPiece tokenization 的含义和区别 SentencePiece 和 WordPiece 都是常用的分词(tokenization)技术,主要用于自然语言处理(NLP)中的文本预处理,尤其是在处理大规模文本数据时。它们都基于子词(subword)单元,能够将未登录词(out-of-vocabulary, O…

视频m3u8形式播放 -- python and html

hls hls官网地址 创建项目 ts为视频片段 m3u8文件内容 html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" …

【知识分享】PCIe5.0 TxRx 电气设计参数汇总

目录 0 引言 1 参考时钟--Refclk 2 发射端通道设计 3 发送均衡技术 4 接收端通道设计 5 接收均衡技术 6 结语 7 参考文献 8 扩展阅读 0 引言 PCI Express Base Specification 5.0的电气规范中&#xff0c;关键技术要点如下&#xff1a; 1. 支持2.5、5.0、8.0、16.0和3…

【HF设计模式】06-命令模式

声明&#xff1a;仅为个人学习总结&#xff0c;还请批判性查看&#xff0c;如有不同观点&#xff0c;欢迎交流。 摘要 《Head First设计模式》第6章笔记&#xff1a;结合示例应用和代码&#xff0c;介绍命令模式&#xff0c;包括遇到的问题、采用的解决方案、遵循的 OO 原则、…

设计模式-模板方法实现

文章目录 模式结构模式特点示例代码输出结果关键点解析模式的优缺点使用场景总结 模板方法模式&#xff08;Template Method Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一个操作中的算法骨架&#xff0c;而将某些步骤的实现延迟到子类中。通过这种方式&…

记一次数据库连接 bug

整个的报错如下&#xff1a; com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Metho…

Java 前端详解

Java 前端详解 Java 前端开发主要涉及使用 Java 相关技术和框架来创建用户界面和处理用户交互。虽然 Java 原本是后端开发的主力语言&#xff0c;但它也提供了许多前端开发工具和框架。以下是 Java 前端开发的主要内容和技术栈。 一、Java 前端技术栈 Java Swing 和 AWT AWT (…

【游戏设计原理】76 - 惩罚

惩罚是玩家在游戏中得到反馈的一种形式&#xff0c;可以认为是一种负反馈。 除了文中提到的几种惩罚机制&#xff08;“生命/游戏结束/继续”、“枯萎”、“永久死亡”&#xff09;&#xff0c;还有其他一些常见的惩罚类型&#xff0c;它们的设计主要目的是增加游戏的挑战性&a…

Java 基于 SpringBoot+Vue 的二手车交易系统(附源码,部署+文档)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【Vim Masterclass 笔记24】S10L43 + L44:同步练习10 —— 基于 Vim 缓冲区的各类基础操作练习(含点评课)

文章目录 S10L43 Exercise 12 - Vim Buffers1 训练目标2 操作指令2.1. 打开 buf* 文件2.2. 查看缓冲区 View the buffers2.3. 切换缓冲区 Switch buffers2.4. 同时编辑多个缓冲区 Edit multiple buffers at once2.5. 缓冲区的增删操作 Add and delete buffers2.6. 练习 Vim 内置…

【Python使用】嘿马python高级进阶全体系教程第11篇:静态Web服务器-面向对象开发,1. 以面向对象的方式开发静态W

本教程的知识点为&#xff1a;操作系统 1. 常见的操作系统 4. 小结 ls命令选项 2. 小结 mkdir和rm命令选项 1. mkdir命令选项 压缩和解压缩命令 1. 压缩格式的介绍 2. tar命令及选项的使用 3. zip和unzip命令及选项的使用 4. 小结 编辑器 vim 1. vim 的介绍 2. vim 的工作模式 …

即现软著工具 - 让软著申请更高效

在软件著作权申请的过程中&#xff0c;开发者常常会遇到代码整理、统计和生成证明文件等繁琐且复杂的任务。为了解决这些问题&#xff0c;提高申请效率和成功率&#xff0c;给大家介绍一款工具&#xff1a;即现软著工具。 即现软著工具&#xff0c;能够快速整理软著申请的程序鉴…