多线程杂谈:惊群现象、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 性能调优&…

视频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 原则、…

记一次数据库连接 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…

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

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

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

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

【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;能够快速整理软著申请的程序鉴…

一部手机如何配置内网电脑同时访问内外网

做过运维的朋友都知道&#xff0c;最麻烦的是运维电脑不能远程&#xff0c;每次都得现场进行维护&#xff0c;明明客户那边有可以访问内网的电脑&#xff0c;怎么操作能将这台电脑能访问跟到外网呢&#xff0c;这样不就能通过远程软件远程了吗&#xff1f;嘿嘿。按以下步骤试试…

Python网络自动化运维---SSH模块

目录 SSH建立过程 实验环境准备 一.SSH模块 1.1.Paramiko模块 1.1.1实验代码 1.1.2代码分段讲解 1.1.3代码运行过程 1.2Netmiko模块 Netmiko模块对比paramiko模块的改进&#xff1a; 1.2.1实验代码 1.2.2代码分段讲解 1.2.3代码运行过程 二.Paramiko模块和Ne…

Esxi下虚拟机磁盘类型厚置备改精简置备

Esxi虚拟机磁盘类型厚置备改精简置备 一、esxi报错磁盘不足 1.1、虚拟机报错磁盘不足 1.2、虚拟机磁盘类型 VMware vSphere 中有两种主要类型的虚拟硬盘&#xff1a;精简配置磁盘和厚置备磁盘。 厚置备磁盘有两种分配模型&#xff1a;厚置备延迟置零和厚置备置零。 三者比…

【MySQL系列文章】Linux环境下安装部署MySQL

前言 本次安装部署主要针对Linux环境进行安装部署操作,系统位数64 getconf LONG_BIT 64MySQL版本&#xff1a;v5.7.38 一、下载MySQL MySQL下载地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions) 二、上传MySQL压缩包到Linuxx环境&#xff0c…

HTML 表单和输入标签详解

HTML 表单是网页与用户交互的重要工具&#xff0c;它允许用户输入数据并将其提交到服务器。表单在网页中的应用非常广泛&#xff0c;例如登录、注册、搜索、评论等功能都离不开表单。本文将详细介绍 HTML 表单及其相关标签的使用方法&#xff0c;帮助你全面掌握表单的设计与实现…