多线程杂谈:惊群现象、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:
		// 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之后可以保证局部静态对象的初始化是线程安全的,只初始化一次(不会获得两个实例)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu -- 几行命令使用Ollama部署本地AI大模型, 仅调用api, 快速测试api效果

需求 需要在本地快速部署一个大模型, 然后使用 局域网 的其他电脑进行 api调用为了快速测试, 大模型选择了 qwen2:0.5B 进行快速测试 开始 下载安装 ollama curl -fsSL https://ollama.com/install.sh | sh验证安装 ollama --version下载安装模型并运行 ollama run qwen2:…

无降智o1 pro——一次特别的ChatGPT专业模式探索

这段时间和朋友们交流 ChatGPT 的使用心得&#xff0c;大家都提到一个很“神秘”的服务&#xff1a;它基于 O1 Pro 模型&#xff0c;能够在对话里一直保持相对高水平的理解和回复&#xff0c;不会突然变得“降智”。同时&#xff0c;整体使用还做了免折腾的网络设置——简单一点…

1. 基于图像的三维重建

1. 基于图像的三维重建 核心概念三维重建中深度图、点云的区别&#xff1f;深度图点云总结 深度图到点云还需要什么步骤&#xff1f;1. **获取相机内参**2. **生成相应的像素坐标**3. **计算三维坐标**4. **构建点云**5. **处理颜色信息&#xff08;可选&#xff09;**6. **去除…

国内有哪些著名的CRM系统提供商?

嘿&#xff0c;你有没有想过&#xff0c;在这个信息爆炸的时代里&#xff0c;企业怎么才能更好地管理客户关系呢&#xff1f;答案就是使用高效的CRM系统。今天我就来给大家聊聊那些在国际上非常有名的CRM系统提供商吧。 悟空CRM 首先不得不提的就是悟空CRM了&#xff01;这可…

QTableWidget的简单使用

1.最简单的表格示例&#xff1a; ui->tableWidget->setRowCount(2);// 设置行数ui->tableWidget->setColumnCount(3);// 设置列数&#xff0c;一定要放在设置行表头之前QStringList rowHeaderList;// 行表头rowHeaderList << QStringLiteral("姓名"…

Jenkins-Pipeline简述

一. 什么是Jenkins pipeline&#xff1a; pipeline在jenkins中是一套插件&#xff0c;主要功能在于&#xff0c;将原本独立运行于单个或者多个节点的任务连接起来&#xff0c;实现单个任务难以完成的复杂发布流程。Pipeline的实现方式是一套Groovy DSL&#xff0c;任何发布流程…

Hadoop美食推荐系统 爬虫1.8w+数据 协同过滤余弦函数推荐美食 Springboot Vue Element-UI前后端分离

Hadoop美食推荐系统 爬虫1.8w数据 协同过滤余弦函数推荐美食 Springboot Vue Element-UI前后端分离 【Hadoop项目】 1. data.csv上传到hadoop集群环境 2. data.csv数据清洗 3.MapReducer数据汇总处理, 将Reducer的结果数据保存到本地Mysql数据库中 4. SpringbootEchartsMySQL 显…

两份PDF文档,如何比对差异,快速定位不同之处?

PDF文档比对是通过专门的工具或软件&#xff0c;自动检测两个PDF文件之间的差异&#xff0c;并以可视化的方式展示出来。这些差异可能包括文本内容的修改、图像的变化、表格数据的调整、格式的改变等。比对工具通常会标记出新增、删除或修改的部分&#xff0c;帮助用户快速定位…

苍穹外卖项目总结(二)

本篇对苍穹外卖后半部分进行介绍&#xff0c;重点是redis缓存的使用以及微信小程序客户端开发。 目录 一、菜品管理 1.1新增菜品 1.2菜品的分页查询 1.3删除菜品 1.4修改菜品 1.5设置营业状态 二、微信小程序客户端的开发 三、Redis的基本使用 常用命令&#xff1a; 缓…

MyBatisPlus简介及入门案例

一、简介 官网&#xff1a;https://baomidou.com/introduce/ 1.简介 MyBatisPlus只做增强&#xff0c;不做改变&#xff0c;为简化开发、提高效率而生 2.特性 (1)无侵入 只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;如丝般顺滑 (2)损耗小 启动…

欧拉(Euler 22.03)安装ProxySQL

下载离线安装包 proxysql-2.0.8-1-centos7.x86_64.rpm 链接: https://pan.baidu.com/s/1R-SJiVUEu24oNnPFlm9wRw 提取码: sa2w离线安装proxysql yum localinstall -y proxysql-2.0.8-1-centos7.x86_64.rpm 启动proxysql并检查状态 systemctl start proxysql 启动proxysql syste…

电子应用设计方案96:智能AI充电器系统设计

智能 AI 充电器系统设计 一、引言 智能 AI 充电器系统旨在为各种电子设备提供高效、安全、智能的充电解决方案&#xff0c;通过融合人工智能技术&#xff0c;实现自适应充电、优化充电效率和保护电池寿命。 二、系统概述 1. 系统目标 - 自适应识别不同设备的充电需求&#xf…

TongESB7.1.0.0如何使用dockercompose运行镜像(by lqw)

文章目录 安装准备安装 安装准备 1.安装好docker和dockercompose&#xff1a; docker、docker-compose安装教程&#xff0c;很详细 2.上传好安装相关文件 安装 使用以下命令导入管理端镜像和运行时镜像 docker load -i tongesb_manage_7100.tar docker load -i tongesb_se…

Python 模拟真人鼠标轨迹算法 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

微软Win10 RP 19045.5435(KB5050081)预览版发布!

系统之家1月20日最新报道&#xff0c;微软面向Release Preview频道的Windows Insider项目成员&#xff0c;发布了适用于Windows10 22H2版本的KB5050081更新&#xff0c;更新后系统版本号将升至19045.5435。本次更新增加了对GB18030-2022标准的支持&#xff0c;同时新版日历将为…

【VRChat · 改模】Unity工程导入人物模型;并添加着色器教程;

一、Unity工程导入人物模型 1.创建一个新的工程文件&#xff08;使用 VRChat 官方的开发工具 VCC&#xff09; 不添加着色器的时候&#xff0c;模型是粉色的 2.导入人物模型 在工程文件的 Assets 目录下&#xff0c;创建一个新的目录&#xff0c;可以起名为你的模型的名字 …

Django简介与虚拟环境安装Django

目录 1.Django简介 1.1 Django 的核心特点 1.2 Django 的核心组件 1.3 Django 的应用场景 1.4 总结 2.基础环境建立 2.1 创建虚拟环境 2.1.1 使用 virtualenv 创建虚拟环境 2.1.2 使用 venv 创建虚拟环境 2.2 激活虚拟环境 2.2.1 在 Windows 上 2.2.2 在 macOS 或 …

使用 ChatGPT 生成和改进你的论文

文章目录 零、前言一、操作引导二、 生成段落或文章片段三、重写段落四、扩展内容五、生成大纲内容六、提高清晰度和精准度七、解决特定的写作挑战八、感受 零、前言 我是虚竹哥&#xff0c;目标是带十万人玩转ChatGPT。 ChatGPT 是一个非常有用的工具&#xff0c;可以帮助你…

宝塔面板修改端口号后无法访问?

解决办法&#xff1a; 1、查询端口是否修改 cat www/server/panel/data/port.pl 显示 当前的端口例如&#xff1a;12123 2.查询防火墙的开放的端口 命令&#xff1a;firewall-cmd --list-ports 3. 使用firewall-cmd --query-port12123/tcp 如显示yes 表示开通&#xff0c;…

Open3D 最小二乘拟合平面(直接求解法)【2025最新版】

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 博客长期更新,本文最近更新时间为:2025年1月18日。 一、算法原理 平面方程的一般表达式为: