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

news2025/1/24 22:40:41

引言

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

目录

引言

惊群现象

结合条件变量

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:
		// RAII
		shared_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 = sp3
		shared_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.coloradmin.cn/o/2281661.html

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

相关文章

腾讯 Hunyuan3D-2: 高分辨率3D 资产生成

腾讯 Hunyuan3D-2&#xff1a;高分辨率 3D 资产生成的突破 前言 在当今数字化时代&#xff0c;3D 资产生成技术正变得越来越重要。无论是游戏开发、影视制作还是虚拟现实领域&#xff0c;高质量的 3D 模型和纹理都是创造沉浸式体验的关键。然而&#xff0c;传统的 3D 资产制作…

R语言学习笔记之开发环境配置

一、概要 整个安装过程及遇到的问题记录 操作步骤备注&#xff08;包含遇到的问题&#xff09;1下载安装R语言2下载安装RStudio3离线安装pacman提示需要安装Rtools4安装Rtoolspacman、tidyfst均离线安装完成5加载tidyfst报错 提示需要安装依赖&#xff0c;试错逐步下载并安装…

DRG/DIP 2.0时代下基于PostgreSQL的成本管理实践与探索(上)

一、引言 1.1 研究背景与意义 在医疗领域的改革进程中&#xff0c; DRG/DIP 2.0 时代&#xff0c;医院成本管理的重要性愈发凸显。新的医保支付方式下&#xff0c;医院的收入不再单纯取决于医疗服务项目的数量&#xff0c;而是与病种的分组、费用标准以及成本控制紧密相关。这…

【数据结构】_顺序表

目录 1. 概念与结构 1.1 静态顺序表 1.2 动态顺序表 2. 动态顺序表实现 2.1 SeqList.h 2.2 SeqList.c 2.3 Test_SeqList.c 3. 顺序表性能分析 线性表是n个具有相同特性的数据元素的有限序列。 常见的线性表有&#xff1a;顺序表、链表、栈、队列、字符串等&#xff1b…

缓存之美:万文详解 Caffeine 实现原理(下)

上篇文章&#xff1a;缓存之美&#xff1a;万文详解 Caffeine 实现原理&#xff08;上&#xff09; getIfPresent 现在我们对 put 方法有了基本了解&#xff0c;现在我们继续深入 getIfPresent 方法&#xff1a; public class TestReadSourceCode {Testpublic void doRead() …

VSCode下EIDE插件开发STM32

VSCode下STM32开发环境搭建 本STM32教程使用vscode的EIDE插件的开发环境&#xff0c;完全免费&#xff0c;有管理代码文件的界面&#xff0c;不需要其它IDE。 视频教程见本人的 VSCodeEIDE开发STM32 安装EIDE插件 Embedded IDE 嵌入式IDE 这个插件可以帮我们管理代码文件&am…

HTTP 配置与应用(局域网)

想做一个自己学习的有关的csdn账号&#xff0c;努力奋斗......会更新我计算机网络实验课程的所有内容&#xff0c;还有其他的学习知识^_^&#xff0c;为自己巩固一下所学知识&#xff0c;下次更新HTTP 配置与应用&#xff08;不同网段&#xff09;。 我是一个萌新小白&#xf…

LiteFlow Spring boot使用方式

文章目录 概述LiteFlow框架的优势规则调用逻辑规则组件定义组件内数据获取通过 DefaultContext自定义上下文 通过 组件规则定义数据通过预先传入数据 liteflow 使用 概述 在每个公司的系统中&#xff0c;总有一些拥有复杂业务逻辑的系统&#xff0c;这些系统承载着核心业务逻…

mysql学习笔记-数据库的设计规范

1、范式简介 在关系型数据库中&#xff0c;关于数据表设计的基本原则、规则就称为范式。 1.1键和相关属性的概念 超键:能唯一标识元组的属性集叫做超键。 候选键:如果超键不包括多余的属性&#xff0c;那么这个超键就是候选键 主键:用户可以从候选键中选择一个作为主键。 外…

计算机网络 (55)流失存储音频/视频

一、定义与特点 定义&#xff1a;流式存储音频/视频是指经过压缩并存储在服务器上的多媒体文件&#xff0c;客户端可以通过互联网边下载边播放这些文件&#xff0c;也称为音频/视频点播。 特点&#xff1a; 边下载边播放&#xff1a;用户无需等待整个文件下载完成即可开始播放…

60,【1】BUUCF web [RCTF2015]EasySQL1

先查看源码 1&#xff0c;changepwd&#xff08;修改密码&#xff09; <?php // 开启会话&#xff0c;以便使用会话变量 session_start();// 设置页面的内容类型为 HTML 并使用 UTF-8 编码 header("Content-Type: text/html; charsetUTF-8");// 引入配置文件&…

我谈概率论与数理统计的知识体系

学习概率统计二十多年后&#xff0c;在廖老师的指导下&#xff0c;厘清了各章之间的关系。本来就是一条线两个分支&#xff0c;脉络很清晰。 分支一&#xff1a;从随机现象到样本空间到随机事件再到概率。 从随机事件到随机变量&#xff1a;为了进行定量的数学处理&#xff0…

基于海思soc的智能产品开发(视频的后续开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们讨论了camera&#xff0c;也讨论了屏幕驱动&#xff0c;这些都是基础的部分。关键是&#xff0c;我们拿到了这些视频数据之后&#xff0c;…

Python的进程和线程

ref 讲个故事先 这就像一个舞台&#xff08;CPU核心&#xff09;​&#xff0c; 要供多个剧组演出多个剧目&#xff08;进程&#xff09;​&#xff0c; 剧目中有多个各自独立的角色&#xff08;线程&#xff09;​&#xff0c;有跑龙套的&#xff0c;有主角&#xff0c;第一…

Xcode :给模拟器 创建桌面 快捷方式

给模拟器 创建 桌面 快捷方式&#xff1a; 1、找到xcode程序&#xff1b; 2、右击鼠标点击”显示包内容“菜单&#xff1b; 3、打开contents/developer/applications/ 找到Simulator工具图标&#xff0c;右击鼠标点击”制作替身“菜单&#xff1b; 4、将替身拖到桌面上。 …

STM32项目分享:智能厨房安全检测系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; STM32智能厨房安全检测系统 &#xff08;资料分…

STM32_SD卡的SDIO通信_基础读写

本篇将使用CubeMXKeil, 创建一个SD卡读写的工程。 目录 一、SD卡要点速读 二、SDIO要点速读 三、SD卡座接线原理图 四、CubeMX新建工程 五、CubeMX 生成 SD卡的SDIO通信部分 六、Keil 编辑工程代码 七、实验效果 实现效果&#xff0c;如下图&#xff1a; 一、SD卡 速读…

ubuntu20.04安装使用direct_visual_lidar_calibration标定雷达和相机

官方链接GitHub - koide3/direct_visual_lidar_calibration: A toolbox for target-less LiDAR-camera calibration [ROS1/ROS2] 官方安装方式 Installation - direct_visual_lidar_calibration 安装依赖 sudo apt install libomp-dev libboost-all-dev libglm-dev libglfw…

华为EC6110T-海思Hi3798MV310_安卓9.0_通刷-强刷固件包

华为EC6110T-海思Hi3798MV310_安卓9.0_通刷-强刷固件包 刷机教程说明&#xff1a; 适用机型&#xff1a;华为EC6110-T、华为EC6110-U、华为EC6110-M 破解总分为两个部分&#xff1a;拆机短接破解&#xff08;保留IPTV&#xff09;和OTT卡刷&#xff08;不保留IPTV&#xff09…

Markdown Viewer 浏览器, vscode

使用VS Code插件打造完美的MarkDown编辑器&#xff08;插件安装、插件配置、markdown语法&#xff09;_vscode markdown-CSDN博客 右键 .md 文件&#xff0c;选择打开 方式 &#xff08;安装一些markdown的插件) vscode如何预览markdown文件 | Fromidea GitCode - 全球开发者…