【C++多线程编程:六种锁】

目录

普通互斥锁: 

轻量级锁

独占锁:

std::lock_guard:

std::unique_lock: 

共享锁:

超时的互斥锁

递归锁


普通互斥锁: 

std::mutex确保任意时刻只有一个线程可以访问共享资源,在多线程中常用于保护共享资源。

互斥锁的实现示例:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex Mutex;
int shared_source = 1;

void Shared_data()
{
	//获取锁
	Mutex.lock();
	shared_source++;
	std::cout << "shared_source : " << shared_source <<" by thread :"<<std::this_thread::get_id()<< std::endl;
	//锁释放
	Mutex.unlock();
}
int main()
{
	std::thread thread1(Shared_data);
	std::thread thread2(Shared_data);

	thread1.join();
	thread2.join();
	return 0;
}

在实际使用的过程中,如果容易忘记对锁进行释放,可以使用std::unique_lock和std::lock_guard安全的管理锁的释放。 

轻量级锁

在C++中,轻量级锁通常指的是一些开销较小,性能较高的锁机制,C++并没有直接提供“轻量级锁”的概念,可以通过一个自旋锁来达到实现轻量级锁的目的。

适用场景:竞争不激烈,比如在大多数时间里中有一个线程需要访问临界区的情况。但如果竞争激烈会导致CUP资源的浪费(CPU自旋),降低性能。

自旋锁:一种简单的锁机制,当线程尝试获取锁时,如果锁已被其他线程占用,则当前线程会不断地循环等待,直到锁可用

自旋锁的示例:

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<bool>spinlock(false);

void spin_lock()
{
	while (spinlock.exchange(true, std::memory_order_acquire))
	{
		//如果锁被占用则一直自旋,等待锁可用
	}
}

void spin_unlock()
{
	spinlock.store(false, std::memory_order_release);//释放锁,并将锁的状态写入内存中
}
void critical_section()
{
	spin_lock();
	std::cout << "mutex acquire by thread : " << std::this_thread::get_id() << std::endl;
//	std::this_thread::sleep_for(std::chrono::seconds(1));
	spin_unlock();//锁释放
}
int main()
{
	std::thread t1(critical_section);
	std::thread t2(critical_section);

	t1.join();
	t2.join();

	return 0;
}

独占锁:

std::unique_lock和std::lock_guard都是cpp标准库提供的管理互斥锁的类,他们都提供了自动锁和解锁的功能。但二者存在一些关键区别:

std::lock_guard:

  • std::lock_guard并不是一种锁,而是一个作用域锁管理,一个能管理锁生命周期的工具,用于简化互斥锁的使用,确保在作用域结束后自动释放锁,避免死锁问题
  • 自动加锁和解锁:在构造时自动加锁,在析构时自动解锁,不需要显示的调用加锁和解锁的方法
  • 不支持条件变量配合使用:条件变量需要可以临时释放锁并重新获取锁,但是lock_guard并没有提供unlock().
  • 使用场景:适用于不需要显示控制加锁和解锁的场景,或者不需要与条件变量配合使用的场景,适用于在某个作用域中同步访问共享资源的场景

lock_guard示例:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int shared_data = 0;

void worker()
{
	std::lock_guard<std::mutex>lock(mtx);//自动加锁
	shared_data++;
	std::cout << "shared_data ++ by thread : " << std::this_thread::get_id() << std::endl;
	//自动解锁,无需显示调用unlock()
}
int main()
{
	std::thread t1(worker);
	std::thread t2(worker);

	t1.join();
	t2.join();
	return 0;
}

std::unique_lock: 

  • std::unique_lock本身不是一种锁,而是一个可选的互斥锁管理器,提供了对互斥锁的更加灵活的控制方式
  • 显示加锁和解锁:提供了lock()和unlock()方法,可以显示的加锁和解锁,也可以在构造时自动加锁
  • 可以与条件变量配合
  • 自动管理锁的生命周期,避免了因忘记解锁而导致的死锁问题
  • 使用场景:需要显示控制加锁和解锁时机,或者需要和条件变量配合使用的场景

unique_lock实例:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int shared_data = 0;
char str = 'a';

//不显示调用加解锁,自动加锁和解锁
void worker()
{
	std::unique_lock<std::mutex>lock(mtx);
	//defer_lock =>延迟锁定操作,需要在特定的时机才锁定互斥锁
	shared_data += 1;
	std::cout << "shared_data ++ :" << shared_data << " ,by thread : " << std::this_thread::get_id() << std::endl;
	std::cout << "str: " << str++ << " ,by thread: " << std::this_thread::get_id() << std::endl;
	
}
//显示调用加锁和解锁
void worker()
{
	std::unique_lock<std::mutex>lock(mtx,std::defer_lock);
	//defer_lock =>延迟锁定操作,需要在特定的时机才锁定互斥锁
	shared_data+=1;
	std::cout << "shared_data ++ :"<<shared_data<<" ,by thread : " << std::this_thread::get_id() << std::endl;
	lock.lock();//延迟锁定
	std::cout << "str: " << str++ <<" ,by thread: "<< std::this_thread::get_id() << std::endl;
	lock.unlock();//显示解锁
}

 int main()
{
	std::thread t1(worker);
	std::thread t2(worker);

	t1.join();
	t2.join();
	return 0;
}

独占锁与mutex相比,更加的安全,它可以避免忘记手动解锁,会在其作用域结束时自动的释放锁  

共享锁:

在C++中,共享锁允许多个线程共享读取资源,但在写入资源时只允许一个线程写入数据,要求独占锁。当某个线程获取了独占锁时,其他线程无法获取任何形式地锁,而当多个线程获取共享锁时,他们可以同时读取共享资源

使用场景:适用于读多写少的场景下,能有效提高多线程程序的性能

示例:

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_mutex mtx;
int shared_data = 0;
void read_data()
{
	//获取共享锁
	std::shared_lock <std::shared_mutex> lock(mtx);
	std::cout << "Reading data : " << shared_data << std::endl;
}
void write_data()
{
	//获取独占锁
	std::unique_lock<std::shared_mutex>lock(mtx);
	shared_data = 24;
	std::cout << "Writing data : " << shared_data << std::endl;
}
int main()
{
	std::thread t1(read_data);
	std::thread t2(read_data);

	std::thread t3(write_data);


	t1.join();
	t2.join();
	t3.join();
	return 0;
}

超时的互斥锁

C++标准库提供了timed_mutex来实现该功能,支持超时机制,当线程尝试获取锁时,可以指定一个超时时间,如果在该时间内无法获取锁,线程将返回并继续执行其他任务。通过使用超时互斥锁,可以有效的避免线程在等待锁时无限地阻塞,提高程序地响应和稳定性 

示例: 

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

//超时锁
//C++标准库提供的互斥锁之一,支持在尝试获取锁时设置超时时间
std::timed_mutex mtx;

void function_time()
{
	//在1s内获取锁
	//try_lock_for在指定时间内获取锁,成功返回true,失败则false
	//参数chrono是一个时间间隔类型的一个域
	if (mtx.try_lock_for(std::chrono::seconds(1)))
	{
		std::cout << "Lock acquired " << std::endl;

		//模拟耗时操作
		//在当前线程休眠这2s内,其他线程无法获取该锁
		//所以当t2尝试获取锁时,t1持有锁并休眠2s,当锁释放后,t2锁获取超时
		std::this_thread::sleep_for(std::chrono::seconds(2));
		mtx.unlock();//释放锁
	}
	else
	{
		std::cout << "Fail to acquire lock within 1s" << std::endl;
	}
}
int main()
{
	//主线程创建的这俩线程几乎同时开始执行function_time函数
	std::thread t1(function_time);
	std::thread t2(function_time);

	t1.join();
	t2.join();
	return 0;
}

递归锁

递归锁:同一个线程多次获取同一把锁,而不会导致死锁。cpp中没有直接提供递归锁的实现,但是可以通过reecursive_mutex来实现递归锁的功能。

reecursive_mutex:一个可重入的互斥锁,允许同一个线程多次调用lock或try_lock来获取锁,不会导致死锁。

使用场景:需要在同一个线程中多次获取锁的场景

递归锁示例:

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex mtx;

void function(int n)//递归
{
	if (n > 0)
	{
		mtx.lock();
		std::cout << "lock acquired by thread : " << std::this_thread::get_id() << " n ; " << n << std::endl;
		function(n - 1);
		mtx.unlock();
	}

}
 int main()
{
	std::thread t1(function,3);
	std::thread t2(function,2);

	t1.join();
	t2.join();
	return 0;
}

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

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

相关文章

Idea日志乱码

问题描述 前提&#xff1a;本人使用windows Idea运行sh文件&#xff0c;指定了utf-8编码&#xff0c;但是运行过程中还是存在中文乱码 Idea的相关配置都已经调整 字体调整为雅黑 文件编码均调整为UTF-8 调整Idea配置文件 但是还是存在乱码&#xff0c;既然Idea相关配置已经…

R4-LSTM学习笔记

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 LSTM-火灾温度预测 导入数据数据可视化设置X、y构建模型调用模型个人总结LSTM 的基本结构细胞状态&#xff08;Cell State&#xff09;LSTM 的优点 导入数据 i…

uniapp实现H5页面内容居中与两边留白,打造类似微信公众号阅读体验

在 UniApp 中&#xff0c;由于需要兼容多端应用&#xff0c;我们通常使用 rpx 作为尺寸单位。然而&#xff0c;在某些情况下&#xff0c;如需要实现内容居中且两边留白时&#xff0c;直接使用 rpx 可能会带来一些限制。这时&#xff0c;我们可以考虑使用 px 或 rem 等单位&…

网工_网络体系结构

2024.01.09&#xff1a;网络工程学习笔记&#xff08;网工老姜&#xff09; 第1节 网络体系结构 1.1 计算机一切皆011.2 网络协议1.3 协议的分层模型1.4 主机1向主机2发送数据过程1.5 本章小结 1.1 计算机一切皆01 在计算机内部&#xff0c;所有的数据最终都是以01的方式存在的…

CI/CD 流水线

CI/CD 流水线 CI 与 CD 的边界CI 持续集成CD&#xff08;持续交付/持续部署&#xff09;自动化流程示例&#xff1a; Jenkins 引入到 CI/CD 流程在本地或服务器上安装 Jenkins。配置 Jenkins 环境流程设计CI 阶段&#xff1a;Jenkins 流水线实现CD 阶段&#xff1a;Jenkins 流水…

编程题-二分查找

题目&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1 解法一&#xff08;循环遍历查找&#xff09;&#xff…

OOM排查思路

K8S 容器的云原生生态&#xff0c;改变了服务的交付方式&#xff0c;自愈能力和自动扩缩等功能简直不要太好用。 有好的地方咱要夸&#xff0c;不好的地方咱也要说&#xff0c;真正的业务是部署于容器内部&#xff0c;而容器之外&#xff0c;又有一逻辑层 Pod 。 对于容器和…

Github Copilot学习笔记

&#xff08;一&#xff09;Prompt Engineering 利用AI工具生成prompt设计好的prompt结构使用MarkDown语法&#xff0c;按Role, Skills, Constrains, Background, Requirements和Demo这几个维度描述需求。然后收输入提示词&#xff1a;作为 [Role], 拥有 [Skills], 严格遵守 […

在 Rider 中使用 C# 创建 Windows 窗体应用 Winforms

1&#xff0c;创建项目 new solution 创建一个解决方案 2&#xff0c;打开设计器 在 Form1.cs 上右键打开设计器 认识一下 Rider 的界面 参考微软官方的例子&#xff0c;添加如下属性&#xff1a;注&#xff1a;这里 Listbox 的大小设置成 120, 94 失败&#xff0c;默认的是 12…

R数据分析:多分类问题预测模型的ROC做法及解释

有同学做了个多分类的预测模型,结局有三个类别,做的模型包括多分类逻辑回归、随机森林和决策树,多分类逻辑回归是用ROC曲线并报告AUC作为模型评估的,后面两种模型报告了混淆矩阵,审稿人就提出要统一模型评估指标。那么肯定是统一成ROC了,刚好借这个机会给大家讲讲ROC在多…

#Java-集合进阶-Map

1.Map 声明1 1.1 双列集合的特点 单列集合一次只能添加一个元素&#xff0c;双列集合一次可以添加一对元素 例&#xff1a; 小米手机2000华为手机5000苹果手机9000 这三对元素&#xff0c;左边的我们称之为键&#xff0c;右边的称为值。他们是一一对应的关系 所以双列集合中…

IntelliJ IDEA和MAVEN基本操作:项目和缓存存储到非C盘

为了将 IntelliJ IDEA 的所有项目和缓存存储到 C 盘以外的地方&#xff0c;以下是你需要调整的设置和步骤&#xff1a; 1. 更改项目默认存储位置 打开 IntelliJ IDEA。点击顶部菜单的 File > Settings &#xff08;Windows&#xff09;或 IntelliJ IDEA > Preferences &…

【Linux系列】`find / -name cacert.pem` 文件搜索

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

RabbitMQ基础(简单易懂)

RabbitMQ高级篇请看&#xff1a; RabbitMQ高级篇-CSDN博客 目录 什么是RabbitMQ&#xff1f; MQ 的核心概念 1. RabbitMQ 的核心组件 2. Exchange 的类型 3. 数据流向说明 如何安装RabbitQueue&#xff1f; WorkQueue&#xff08;工作队列&#xff09;&#xff1a; Fa…

《Spring Framework实战》5:Spring Framework 概述

欢迎观看《Spring Framework实战》视频教程 Spring 使创建 Java 企业应用程序变得容易。它为您提供一切 需要在企业环境中采用 Java 语言&#xff0c;并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言&#xff0c;并且可以灵活地创建许多 类型的架构。从 Spring Framework 6.0 开…

有限元分析学习——Anasys Workbanch第一阶段笔记(10)桌子载荷案例分析_实际载荷与均布载荷的对比

目录 0 序言 1 桌子案例 2 模型简化 3 方案A 前处理 1&#xff09;分析类型选择 2&#xff09;材料加载 3&#xff09;约束、载荷及接触 4&#xff09;控制网格(网格大小需要根据结果不断调整) 初始计算结果 加密后计算结果 4 方案B、C 前处理 1&#xff09;分析…

Git 基础——《Pro Git》

⭐获取 Git 仓库 获取 Git 仓库有两种方式&#xff1a; 将未进行版本控制的本地目录转换为 Git 仓库。从其他服务器克隆一个已存在的 Git 仓库。 在已存在目录中初始化 Git 仓库 进入目标目录 在 Linux 上&#xff1a;$ cd /home/user/my_project在 macOS 上&#xff1a;$ c…

Java 将RTF文档转换为Word、PDF、HTML、图片

RTF文档因其跨平台兼容性而广泛使用&#xff0c;但有时在不同的应用场景可能需要特定的文档格式。例如&#xff0c;Word文档适合编辑和协作&#xff0c;PDF文档适合打印和分发&#xff0c;HTML文档适合在线展示&#xff0c;图片格式则适合社交媒体分享。因此我们可能会需要将RT…

R语言在森林生态研究中的魔法:结构、功能与稳定性分析——发现数据背后的生态故事!

森林生态系统结构、功能与稳定性分析与可视化研究具有多方面的重要意义&#xff0c;具体如下&#xff1a; 一、理论意义 ●深化生态学理论 通过研究森林生态系统的结构、功能与稳定性&#xff0c;可以深化对生态系统基本理论的理解。例如&#xff0c;生物多样性与生态系统稳定性…

Delphi+SQL Server实现的(GUI)户籍管理系统

1.项目简介 本项目是一个户籍管理系统&#xff0c;用于记录住户身份信息&#xff0c;提供新户登记&#xff08;增加&#xff09;、户籍变更&#xff08;修改&#xff09;、户籍注销&#xff08;删除&#xff09;、户籍查询、曾用名查询、迁户记录查询以及创建备份、删除备份共8…