音视频学习笔记——c++多线程(二)

✊✊✊🌈大家好!本篇文章是多线程系列第二篇文章😇。首先讲解了利用mutex解决多线程数据共享问题,举例更好理解lockunlock的使用方法,以及错误操作造成的死锁问题,最后讲解了lock_guardunique_lock使用的注意事项。

c++多线程系列目录:

c++多线程(一)多进程和多线程并发**的区别以及各自优缺点,Thead线程库的基本使用。

对多线程其他内容感兴趣的同学可以点击上方目录链接跳转。


本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育


🎡导航小助手🎡

    • 一、互斥量(Mutex)
      • 1.1 lock和unlock
      • 1.2 死锁
      • 1.3lock_guard与unique_lock
    • 二、小结

一、互斥量(Mutex)

   当多个线程同时访问同一个变量,并且其中至少有一个线程对该变量进行了写操作,那么就会出现数据竞争问题。数据竞争可能会导致程序崩溃、产生未定义的结果,或者得到错误的结果。
   为了避免数据竞争问题,需要使用同步机制来确保多个线程之间对共享数据的访问是安全的。常见的同步机制包括互斥量、条件变量、原子操作等。
   互斥量(mutex)是一种用于实现多线程同步的机制,用于确保多个线程之间对共享资源的访问互斥。互斥量通常用于保护共享数据的访问,以避免多个线程同时访问同一个变量或者数据结构而导致的数据竞争问题。

1.1 lock和unlock

mutex常用操作:

  • lock():资源上锁
  • unlock():解锁资源
  • trylock():查看是否上锁,它有下列3种类情况:
    • (1)未上锁返回false,并锁住;
    • (2)其他线程已经上锁,返回true
    • (3)同一个线程已经对它上锁,将会产生死锁

   死锁:在两个或两个以上的进程在执行过程中,由于竞争资源或者彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
下面举一个实例:
添加lock()和unlock():

	#include <iostream>
	#include <thread>
	#include <mutex>
	using namespace std;
	int shared_data = 0;
	mutex mtx;
	void func(int n) {
	    for (int i = 0; i < 10; ++i) {
	        mtx.lock();//添加lock锁
	        shared_data++;        
	        cout << "Thread " << n 
	        << " increment shared_data to " << shared_data <<endl;
	        mtx.unlock();//解锁
	    }
	}
	int main() {
	    thread t1(func, 1);
	    thread t2(func, 2);
	
	    t1.join();
	    t2.join();    
	    cout << "Final shared_data = " << shared_data <<endl;    
	    return 0;
	}

运行结果:
在这里插入图片描述
不添加:
在这里插入图片描述
结果就会很乱,因为两个线程都对shared_data进行操作,发生了数据竞争现象。

补充:什么是线程安全?
如果多线程程序每次的运行结果和单线程运行的结果始终是一样的,那么线程是安全的。

1.2 死锁

假设存在两个线程 T1 和 T2,都要对两个互斥量 mtx1 和 mtx2 进行访问,且按照以下顺序获取互斥量的所有权:

  • T1 先获取 mtx1 的所有权,再获取 mtx2 的所有权。
  • T2 先获取 mtx2 的所有权,再获取 mtx1 的所有权。

如果两个线程同时执行,就会出现死锁问题。
因为 T1 获取了 mtx1 的所有权,但是无法获取 mtx2 的所有权,而 T2 获取了 mtx2 的所有权,但是无法获取 mtx1 的所有权,两个线程互相等待对方释放互斥量,导致死锁。

为了解决这一问题,就需要两个线程按照相同的顺序获取互斥量的所有权。

	#include <iostream>
	#include <thread>
	#include <mutex>
	std::mutex mtx1, mtx2;
	void func1(){
	    mtx2.lock(); 
	    std::cout << "Thread 1 locked mutex 2" << std::endl;    
	    mtx1.lock();    
	    std::cout << "Thread 1 locked mutex 1" << std::endl;    
	    mtx1.unlock();    
	    std::cout << "Thread 1 unlocked mutex 1" << std::endl;    
	    mtx2.unlock();    
	    std::cout << "Thread 1 unlocked mutex 2" << std::endl;
	}
	void func2() {    
	    mtx2.lock();    
	    std::cout << "Thread 2 locked mutex 2" << std::endl;    
	    mtx1.lock();    
	    std::cout << "Thread 2 locked mutex 1" << std::endl;    
	    mtx1.unlock();    
	    std::cout << "Thread 2 unlocked mutex 1" << std::endl;    
	    mtx2.unlock();    
	    std::cout << "Thread 2 unlocked mutex 2" << std::endl;
	}
	int main(){    
	    std::thread t1(func1);    
	    std::thread t2(func2);    
	    t1.join();    
	    t2.join();    
	    return 0;
	}

运行结果:
在这里插入图片描述

1.3lock_guard与unique_lock

lock_guard
创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。
lock_guard的特点:

  • 当构造函数被调用时,该互斥量会被自动锁定
  • 当析构函数被调用时,该互斥量会被自动解锁
  • std::lock_guard 对象不能复制或移动,因此它只能在局部作用域中使用。

代码举例:

	#include <thread>
	#include <mutex>
	#include <iostream>
	int g_i = 0;
	std::mutex g_i_mutex; // protects g_i,用来保护g_i
	void safe_increment() {
		const std::lock_guard<std::mutex> lock(g_i_mutex);
		++g_i;
		std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
		// g_i_mutex自动解锁
	}
	int main() {
		std::cout << "main id: " << std::this_thread::get_id() << std::endl;
		std::cout << "main: " << g_i << '\n';
		std::thread t1(safe_increment);
		std::thread t2(safe_increment);
		t1.join(); 
		t2.join();
		std::cout << "main: " << g_i << '\n';
	}

运行结果:

在这里插入图片描述
最开始,主线程id17336g_i0,每经过一个线程,g_i++

unique_lock
简单地讲,unique_lock lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,它可以对互斥量进行更加灵活的管理,包括延迟加锁、条件变量、超时等。
unique_lock的特点:

  • 创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定
  • 可以随时加锁解锁
  • 作用域规则同 lock_grard,析构时自动释放锁
  • 不可复制,可移动
  • 条件变量需要该类型的锁作为参数(此时必须使用unique_lock)

std::unique_lock 提供了以下几个成员函数:

  • lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁
  • try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true
  • try_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间
  • try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点
  • unlock():对互斥量进行解锁操作
	#include <thread>
	#include <mutex>
	#include <iostream>
	int g_i = 0;
	std::mutex mtx;
	void func() {
		for (int i = 0; i < 10; i++) {
			std::unique_lock<std::mutex> lg(mtx);
			//知识点1.构造但不加锁,需要自己加锁
			//std::unique_lock<std::mutex> lg(mtx,std::defer_lock);
			g_i++;
		}
	}
	
	//知识点2,延时加锁
	std::timed_mutex  mtx1;  //需要使用时间锁
	void func1(){
		for (int i = 0; i < 2; i++) {
			std::unique_lock<std::timed_mutex> lg(mtx1, std::defer_lock);
			//知识点2,延时加锁
			if (lg.try_lock_for(std::chrono::seconds(2))) {
				std::this_thread::sleep_for(std::chrono::seconds(1));
				g_i++;
			}
		}
	}
	
	int main() {
		std::thread t1(func1);
		std::thread t2(func1);
		t1.join();
		t2.join();
		std::cout << g_i << '\n';
	}

总之,一定要记住。unique_lock会在构建的时候可以选择是否进行加锁,析构的时候会解锁,并且可以选择延迟加锁。

二、小结

  1. 互斥量(mutex)是一种用于实现多线程同步的机制,用于确保多个线程之间对共享资源的访问互斥。互斥量通常用于保护共享数据的访问,以避免多个线程同时访问同一个变量或者数据结构而导致的数据竞争问题。
  2. 常常使用lock和unlock进行上锁和解锁,错误的行为有时会造成死锁,这就要要求两个线程按照相同的顺序获取互斥量的所有权。
  3. 创建lock_guard对象时,它会自动上锁,析构时自动解锁,比较方便。
  4. unique_lock**会在构建的时候可以选择是否进行加锁,析构的时候会解锁,并且可以选择延迟加锁。适用范围更广。

感谢大家阅读!
接下来还会继续更新多线程相关知识,感兴趣的可以看其他笔记!

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

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

相关文章

PromptBreeder---针对特定领域演化和发展提示词的方法

原文地址&#xff1a;promptbreeder-evolves-adapts-prompts-for-a-given-domain 论文地址&#xff1a;https://arxiv.org/pdf/2309.16797.pdf 2023 年 10 月 6 日 提示方法分为两大类 硬提示是由人工精心设计的文本提示&#xff0c;包含离散的输入令牌&#xff1b;其缺点…

黑马点评-发布探店笔记

探店笔记 探店笔记类似点评网站的评价&#xff0c;往往是图文结合。 对应的表有两个&#xff1a; tb_blog&#xff1a;探店笔记表&#xff0c;包含笔记中的标题、文字、图片等 tb_blog_comments&#xff1a;其他用户对探店笔记的评价 流程如下&#xff1a; 上传接口&#…

基于SSM框架的动物医疗平台设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 Ajax 3 1.2 MVC设计模式 3 1.3 BootStrap 3 1.4 SSM框架 4 1.5 本章小结 4 2 系统分析 5 2.1 需求分析 5 2.1.1 用户需求分析 5 2.1.2 医生需求分析 6 2.1.3 管理员需求分析 7 2.2 用例分析 8 2.3 非功能需求 10 2.4 本章…

解决火狐浏览器访问地址受限制问题(This address is restricted)

问题如下图&#xff1a; This address is restrictedThis address uses a network port which is normally used for purposes other than Web browsing. Firefox has canceled the request for your protection. 此地址受到限制 此地址使用通常用于 Web 浏览以外的目的的网…

sort函数详解

往期文章推荐&#xff1a; [C] 非常实用的知识点-CSDN博客 1.8编程基础之多维数组————14:扫雷游戏地雷数计算-CSDN博客 &#xff08;并不怎么华丽的分割线&#xff09; 前言 话说在C中有这么一类算法&#xff0c;叫做排序算法。 它有许多分支&#xff1a;冒泡排序&a…

激光在SIC晶圆制造中的应用

碳化硅是一种性能优异的第三代半导体材料&#xff0c;具有光学性能良好、化学惰性大、物理特性优良的特点&#xff0c;包括带隙宽、击穿电压高、热导率高和耐高温性能强等优点&#xff0c;常作为新一代高频、高功率器件的衬底材料&#xff0c;广泛应用在高端制造业领域&#xf…

学术论文GPT的源码解读与二次开发:从ChatPaper到gpt_academic

前言 本文的前两个部分最早是属于此旧文的《学术论文GPT的源码解读与微调&#xff1a;从ChatPaper到七月论文审稿GPT第1版》&#xff0c;但为了每一篇文章各自的内容更好的呈现&#xff0c;于是我今天做了以下三个改动 原来属于mamba第五部分的「Mamba近似工作之线性Transfor…

AcWing 1262. 鱼塘钓鱼(每日一题)

目录 暴力枚举法&#xff1a; 贪心&#xff1a; 原题链接&#xff1a;1262. 鱼塘钓鱼 - AcWing题库 有 N个鱼塘排成一排&#xff0c;每个鱼塘中有一定数量的鱼&#xff0c;例如&#xff1a;N5 时&#xff0c;如下表&#xff1a; 鱼塘编号12345第1分钟能钓到的鱼的数量&…

2024年最新指南:如何订阅Midjourney(详尽步骤解析)

前言&#xff1a; Midjourney是一个基于人工智能的图像生成工具&#xff0c;它使用高级算法来创建独特和复杂的图像。这个工具能够根据用户输入的文字描述生成对应的图片。Midjourney的特点在于它能够处理非常抽象或者具体的描述&#xff0c;生成高质量、富有创意的视觉内容。M…

AI跟踪报道第32期-新加坡内哥谈技术-本周AI新闻:超越GPT4的Claude

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

拿捏算法的复杂度

目录 前言 一&#xff1a;算法的时间复杂度 1.定义 2.简单的算法可以数循环的次数&#xff0c;其余需要经过计算得出表达式 3.记法&#xff1a;大O的渐近表示法 表示规则&#xff1a;对得出的时间复杂度的函数表达式&#xff0c;只关注最高阶&#xff0c;其余项和最高阶…

Halcon基本语法

Halcon是什么&#xff1f; Halcon&#xff08;全称为Halcon Imaging Library&#xff09;是由德国MVTec Software GmbH开发的一套功能强大的机器视觉软件库。Halcon提供了丰富的图像处理和机器视觉算法&#xff0c;用于解决各种工业和科学领域中的视觉检测、识别和测量等问题。…

python数据结构--栈

栈简介 栈类似于一个箱子&#xff0c;我们向里面放书&#xff0c;我们最先放进去的书是在最底下的&#xff0c;所以我们想要拿出来就只能最后一个拿出来&#xff0c;每次放和取都只能操作最上面那个。 特点&#xff1a;先进后出 名词概念&#xff1a;进栈&#xff08;放书&a…

掌握Java建造者模式:逐步构建复杂对象的艺术与实践

建造者模式的主要目的是将一个复杂对象的构建过程封装起来&#xff0c;使得客户端代码不需要知道对象创建的细节。这种模式特别适用于那些具有多个组成部分、创建过程复杂、对象属性多且大多数属性可选的场合。 在Java中&#xff0c;建造者模式通常涉及以下几个角色&#xff1…

Day29:安全开发-JS应用DOM树加密编码库断点调试逆向分析元素属性操作

目录 JS原生开发-DOM树-用户交互 JS导入库开发-编码加密-逆向调试 思维导图 JS知识点&#xff1a; 功能&#xff1a;登录验证&#xff0c;文件操作&#xff0c;SQL操作&#xff0c;云应用接入&#xff0c;框架开发&#xff0c;打包器使用等 技术&#xff1a;原生开发&#x…

SLAM|初识SLAM

在空间中&#xff0c;人可以通过固定不动的事物来作为参考系中的参照物。 而这些固定不动的东西可以称之为特征&#xff0c;空间可以理解成特征存在的空间。 而参照物的意义&#xff0c;可以变成是看到某某参照物&#xff0c;就按这个某某参照物进行位置移动。 比如说碰到这个…

【SQL】550. 游戏玩法分析 IV (关键点:确定连续两次登录)

前述 常见函数用法示例&#xff1a; DATEDIFF(col1, col2) 1DATE_ADD(MIN(col), INTERVAL 1 DAY)ROUND(3.1415926,3) > 四舍五入得到 3.142 题目描述 leetcode原题&#xff1a;550. 游戏玩法分析 IV 思路 确定连续两次登录统计&#xff0c;保留两位小数 写法一 关键…

【Tauri】(4):使用Tauri1.5版本+candle框架运行大模型,前后的搭建运行成功,整合前端项目,在应用中显示。

1&#xff0c;视频地址 关于tauri 框架 2&#xff0c;搭建rust 环境 # 设置服务器国内代理&#xff1a; export RUSTUP_DIST_SERVER"https://rsproxy.cn" export RUSTUP_UPDATE_ROOT"https://rsproxy.cn/rustup"# 设置环境变量 export RUSTUP_HOME/data/…

【Haproxy】Haproxy的配置和应用

HAProxy介绍 HAProxy是法国开发者威利塔罗(Willy Tarreau)在2000年使用C语言开发的一个开源软件&#xff0c;是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&#xff0c;自动故障切换&#xff0c;支持正则表达式及web状态统计&a…

HCIA-HarmonyOS设备开发认证V2.0-习题2

目录 习题一习题二坚持就有收获 习题一 # 判断题## 1.PWM占空比指的是低电平时间占周期时间的百分比。(错误)正确(True)错误(False)解题&#xff1a; - PWM占空比指的是高电平时间占周期时间的百分比## 2.UART是通用异步收发传输器&#xff0c;是通用串行数据总线&#xff0c;…
最新文章