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

news2024/11/26 10:41:09

✊✊✊🌈大家好!本篇文章是多线程系列第二篇文章😇。首先讲解了利用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.coloradmin.cn/o/1511716.html

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

相关文章

LEETCODE3

法一:记忆化递归 int climbStairsRecursive(int n, int* memo) {if (n < 2) {return n;}if (memo[n] > 0) {return memo[n];}memo[n] climbStairsRecursive(n - 1, memo) climbStairsRecursive(n - 2, memo);return memo[n]; }int climbStairs(int n) {int* memo (in…

QML 控件添加键盘事件

在QML中&#xff0c;可以使用Keys类型来处理键盘事件。以下是一个简单的示例&#xff0c;演示如何在QML控件中添加键盘事件&#xff1a; import QtQuick 2.12 import QtQuick.Window 2.12Window {visible: truewidth: 640height: 480title: qsTr("Hello World")Recta…

Linux的MySQL安装与卸载

安装与卸载 卸载安装配置yum源安装MySQL 声明一下本人用的Linux版本是CentOs7.9版本的。 卸载 如果我们用的云服务器&#xff0c;云服务器可能会自带MySQL或者mariadb&#xff08;其实就是MySQL的一个开源分支&#xff09;&#xff0c;如果我们不想用自带的&#xff0c;需要先…

理论学习 BatchNorm2d

import torch import torch.nn as nn# With Learnable Parameters m nn.BatchNorm2d(100) # Without Learnable Parameters m nn.BatchNorm2d(100, affineFalse) input torch.randn(20, 100, 35, 45) output m(input)print(output) print(output.shape)这段代码展示了如何使…

mybatis-plus-generator 使用 velocity 生成前后台代码

操作步骤 1&#xff09;准备mybatis-plus 生成代码的 vm文件 2&#xff09;添加依赖 mybatis-plus-generator 代码生成器的依赖 3&#xff09;执行工具方法生成代码 1、准备 mybatis-plus 生成代码的 vm文件 1&#xff09;找vm模板 去工程的 external Libraries 找到 mybati…

ES6基础6

Promise对象 Promise的含义 所谓Promise&#xff0c;简单说就是一个容器&#xff0c;里面保存着某个未来才会结束的事件&#xff08;通常是一个异步操作&#xff09;的结果。从语法上说&#xff0c;Promise是一个对象&#xff0c;从它可以获取异步操作的消息。Promise提供统一的…

深度学习笔记_8隐马尔可夫模型(HMM)

隐马尔可夫模型(Hidden Markov Model, HMM)是一种统计模型&#xff0c;在语音识别、行为识别、NLP、故障诊断等领域具有高效的性能。 HMM是关于时序的概率模型&#xff0c;描述一个含有未知参数的马尔可夫链所生成的不可观测的状态随机序列&#xff0c;再由各个状态生成观测随…

设计模式 -- 1:简单工厂模式

目录 代码记录代码部分 代码记录 设计模式的代码注意要运用到面向对象的思想 考虑到紧耦合和松耦合 把具体的操作类分开 不让其互相影响&#xff08;注意这点&#xff09; 下面是UML类图 代码部分 #include <iostream> #include <memory> // 引入智能指针的头文…

linux 模拟shell

&#x1f493;博主CSDN主页:麻辣韭菜-CSDN博客&#x1f493;   ⏩专栏分类&#xff1a;http://t.csdnimg.cn/G90eI⏪   &#x1f69a;代码仓库:Linux: Linux日常代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d;&#x1f5…

[linux] socket 非阻塞模式使用注意事项

在使用 socket 的一些 api 的时候&#xff0c;默认情况下都是阻塞模式。比如使用 tcp socket 时&#xff0c;客户端调用 connect() 创建连接&#xff0c;connect() 返回的时候要么是创建连接成功了&#xff0c;要么是出现了错误&#xff0c;反正 connect() 返回的时候结果是确定…

Unsupervised RL:METRA: Scalable Unsupervised RL with Metric-Aware Abstraction

ICLR 2024 Oral paper Intro 无监督RL旨在发现潜在的行为帮助提高下游任务效率以往方法集中于探索以及基于互信息的技能发现(skill)。然而去前者在高危复杂空间实现困难&#xff0c;后者也容易因为缺乏激励导致探索能力不足。本文提出METRA核心观点认为与其在复杂状态空间处理…

OGNL表达式

文章目录 一、简介二、快速入门三、详细使用3.1 基本数据类型3.2 对象类型3.3 List集合3.4 Set集合3.5 Map集合3.6 数组3.7 静态调用3.8 算术运算3.9 逻辑运算3.10 同时执行多个表达式3.11 位运算 Ognl使用总结 - 源链接在此 >> 一、简介 OGNL&#xff08;Object-Graph…

小兔鲜鲜项目(前端vue3)

成果图 大家喜欢给一个赞被&#xff0c; 项目地址&#xff1a;gitee 注意&#xff1a;项目克隆下去之后先运行 npm i之后安装项目插件包之后在npm run dev 运行就可以了

“成像光谱遥感技术中的AI革命:ChatGPT应用指

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。本文重点介绍ChatGPT在遥感中的应用&#xff0c;人工智能…

python读取execl里的图片

正常的读取图片 from openpyxl import load_workbook from PIL import Imagefrom openpyxl import load_workbook wb load_workbook(rC:\Users\Administrator\Downloads\output1111.xlsx) ws wb[wb.sheetnames[0]] for image in ws._images:data image.anchor._fromif image…

深入学习默认成员函数——c++指南

前言&#xff1a;类和对象是面向对象语言的重要概念。 c身为一门既面向过程&#xff0c;又面向对象的语言。 想要学习c&#xff0c; 首先同样要先了解类和对象。 本节就类和对象的几种构造函数相关内容进行深入的解析。 目录 类和对象的基本概念 封装 类域和类体 访问限定符…

家长应如何培养孩子对人工智能(AI)的兴趣?无际Ai分享

随着科技的飞速发展&#xff0c;人工智能已经成为了当今社会的重要组成部分。然而&#xff0c;在中小学阶段&#xff0c;很少有学校系统地对学生进行人工智能方面的教育。作为普通家庭的家长&#xff0c;我们可以通过一些方法来激发孩子对人工智能的兴趣&#xff0c;让他们在这…

网络工程师——2024自学

一、怎样从零开始学习网络工程师 当今社会&#xff0c;人人离不开网络。整个IT互联网行业&#xff0c;最好入门的&#xff0c;网络工程师算是一个了。 什么是网络工程师呢&#xff0c;简单来说&#xff0c;就是互联网从设计、建设到运行和维护&#xff0c;都需要网络工程师来…

第7讲:数组和函数实践:扫雷游戏

第7讲&#xff1a;数组和函数实践&#xff1a;扫雷游戏 1. 扫雷游戏分析和设计1.1 扫雷游戏的功能说明1.2 游戏的分析和设计1.2.1 数据结构的分析1.2.2 文件结构设计 2. 扫雷游戏的代码实现3. 扫雷游戏的扩展 1. 扫雷游戏分析和设计 1.1 扫雷游戏的功能说明 • 使用控制台实现…

Edu 12 --- Simple Subset -- 题解 (一个比较巧妙的思维算法题)

Simple Subset&#xff1a; 题解&#xff1a; 思路解析&#xff1a; 题目要求任意两个数的和为质数&#xff0c;那我们最坏情况就是任意选择一个数&#xff0c;此时子集为最大。 如果子集中有两个奇数或者偶数&#xff0c;他们两个之和一定会被2整除&#xff0c;那么我们只能…