线程安全--互斥锁

在这里插入图片描述

文章目录

  • 一.线程安全问题
      • 读取无效(脏)数据
      • 丢失更新
      • 线程安全的保证--操作的原子性
  • 二.互斥锁及其实现原理
    • 互斥锁的实现原理
    • pthread线程库提供的锁操作
  • 三.死锁问题

在这里插入图片描述

一.线程安全问题

  • 当多个线程并发地对同一个共享资源进行修改操作时,可能会引发数据读写错误(比如读取无效(脏)数据,丢失更新等等)

读取无效(脏)数据

  • 多个线程修改同一个全局变量的示例(模拟抢票):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.cpp"using namespace std;//四个线程模拟抢票
#define NUM 4
//用于记录线程信息的类
class threadData
{
public:threadData(int number){threadname = "thread-" + to_string(number);}
public:string threadname;
};//10张票作为临界资源
int Tickets = 10;//线程执行流
void * GetTickets(void * args){//获取线程名threadData * TName = static_cast<threadData *>(args);//执行抢票逻辑while(true){if(Tickets > 0){usleep(10000);Tickets--;//修改临界资源cout << TName->threadname << "Get one ticket, tickets left:" << Tickets << endl; }else{break;}usleep(10000);}cout << TName->threadname << "exit" << endl;return nullptr;
}int main(){//线程名数组vector<threadData*> threadName(NUM);//线程标识符数组vector<pthread_t> threads(NUM);//创建4个线程for(int i = 0; i < NUM; ++i){threadName[i] = new threadData(i+1);pthread_create(&threads[i],nullptr,GetTickets,threadName[i]);}//轮询阻塞线程等待for(int i =0 ; i < NUM ; ++i){pthread_join(threads[i],nullptr);}//线程名结构体释放for (auto td : threadName){delete td;}return 0;
}

在这里插入图片描述

  • 代码逻辑限制共享变量Tickets不能小于零,但实际执行结果显示共享变量Tickets多线程环境中被减到了-1,引发该错误的原因如下图所示:
    在这里插入图片描述
  • 线程在if(Tickets > 0)处读取到了无效的数据

丢失更新

  • C/C++中对共享变量的++,--操作也是非线程安全的,Var++的汇编代码:
    在这里插入图片描述
    在这里插入图片描述

  • 两个线程并发对共享变量int Var = 10进行++操作引发的丢失更新问题

时间线程1线程2Var的值
1Mov [Var] ,%eax (CPU调度切换至线程2)10
2Mov [Var] ,%eax (CPU调度切换至线程1)10
3Inc %eax10
4Mov %eax,[Var]11
5Inc %eax11
6Mov %eax,[Var]11
  • 两次++并发操作只有一次有效

线程安全的保证–操作的原子性

  • 在多线程环境中,要确保线程安全,各个线程对于同一共享资源的修改操作必须是串行执行的(或者执行过程是可串行化的),即同一时刻只能有一个线程同一共享资源进行修改操作.
    • 满足这样性质的操作称为原子性操作,原子性操作:不可拆分的最小执行单位(一次操作在某个线程中执行完毕之前不可被其他线程重入)
    • 在计算机系统中,最基本的原子性操作就是一条汇编语句,一条汇编语句的执行是不会因为CPU执行流调度切换而中断的,因而是线程安全的,其他操作的原子性只能通过互斥锁来保证

二.互斥锁及其实现原理

互斥锁的实现原理

  • 锁的本质是进程中的共享资源,可以理解为内存中的一个0/1标记位,对于进程而言锁是一个全局变量

  • 线程加锁的本质是将内存中的的锁变量(值为1)交换到CPU中的某个特定的寄存器中(寄存器的初始值为0),当线程被切换时,会将它在CPU中的执行流上下文信息(包括锁标记1)保存到PCB中,相当于线程"带着锁一起被切换掉了"

  • 因此线程持有锁的本质是:线程在CPU中的执行流上下文(各寄存器和缓存中的内容)中带有锁标记,锁资源和线程的绑定关系体现在操作系统的内核层面

  • 线程解锁的本质是将特定的寄存器中的锁标记1交换回内存中的的锁变量(值为0)中

  • 上述的0/1标记位的交换过程是在一条汇编语句中完成的,保证了加锁和解锁过程的原子性,因而是线程安全的
    在这里插入图片描述
    在这里插入图片描述

  • 当内存中的锁变量为0时,其他线程申请锁时就会进入等待队列中休眠直到申请到锁后才能继续执行后续代码,从而在多线程环境中保证了加锁代码段的串行执行.

pthread线程库提供的锁操作

  • 定义全局的锁变量并初始化:
    • pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
  • 代码段的加锁和解锁:
    //代码段加锁,防止线程重入pthread_mutex_lock(&lock);//临界区代码段,同一时刻只能有一个线程在执行//代码段解锁,防止线程重入pthread_mutex_unlock(&lock);
  • 加锁后的模拟抢票代码:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 加锁后,线程等待休眠的可能性增大了,为了保证效率和系统并发量,保证线程安全的前提下,加锁的临界区中的代码量应尽可能少

三.死锁问题

  • 一种常见的死锁情况是:当各线程等待锁资源的逻辑链出现回路时,发生死锁
时间线程1线程2
1申请锁1(申请成功)
2申请锁2(申请成功)
3申请锁2(等待锁资源)
4申请锁1(等待锁资源)(死锁)
5
6

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

多维时序 | Matlab实现GRO-CNN-BiLSTM-Attention淘金算法优化卷积神经网络-双向长短期记忆网络结合注意力机制多变量时间序列预测

多维时序 | Matlab实现GRO-CNN-BiLSTM-Attention淘金算法优化卷积神经网络-双向长短期记忆网络结合注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现GRO-CNN-BiLSTM-Attention淘金算法优化卷积神经网络-双向长短期记忆网络结合注意力机制多变量时间序列预测效果一览基…

基于随机抽样或最小二乘法 c++实现三维点云平面检测

随机抽样 std::vector<int> random(int n, int N){std::vector<int> rets;for(int i0; i<N; i){while(true){int v rand() % n;if(std::find(rets.begin(), rets.end(), v) rets.end()){rets.push_back(v);break;}}}return rets; } bool Plane(std::vector&l…

数据安全保障的具体措施有哪些

随着信息化时代的到来&#xff0c;数据已经成为企业和社会发展的重要资产。然而&#xff0c;数据安全问题也日益突出&#xff0c;如何保障数据的安全性、完整性和可用性成为了亟待解决的问题。以下将详细探讨数据安全保障的各个方面&#xff0c;以期为企业和社会提供更好的数据…

飞桨分子动力学模拟-论文复现第六期:复现TorchMD

飞桨分子动力学模拟-论文复现第六期&#xff1a;复现TorchMD Paddle for MD 飞桨分子动力学模拟科学计算 复现论文-TorchMD: A deep learning framework for molecular simulations 本项目可在AIStudio一键运行&#xff1a;飞桨分子动力学模拟PaddleMD-复现TorchMD 【论文复…

分布式系统的前世

文章目录 前言分布式系统解决了什么问题分布式系统存在什么问题总结 前言 大家好&#xff0c;我是醉墨居士&#xff0c;我准备和大家浅聊一下分布式系统&#xff0c;分享我一下我的心得体会&#x1fae0; 分布式系统解决了什么问题 如果用户的请求压力过于庞大&#xff0c;使…

原生Ajax的使用,四种请求方法示例(前后端代码)

目录 原生Ajax是什么 原生Ajax的优点 Ajax应用环境 Ajax的使用 基本使用步骤 AJAX请求状态和HTTP状态码 AJAX 请求状态 HTTP 状态码 XHR对象的方法 各种请求方式和数据获取 post请求 post 请求完整代码 get 请求 服务端 put 请求 服务端 delete 请求 服务端代…

TypeScript基础知识:类型守卫和类型推断

在 TypeScript 中&#xff0c;类型守卫和类型推断是两个重要的概念&#xff0c;它们可以帮助我们更好地理解和利用类型系统的优势。本文将详细介绍这两个概念&#xff0c;并提供示例代码来说明它们的用法和优势。 一、类型守卫 类型守卫是一种在 TypeScript 中用于缩小变量类型…

U盘安装XP纯净版系统教程软件安装教程(附软件下载地址)

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; U盘安装XP纯净版系统是一种便捷且快速的方式&#xff0c;以实现系统重装或升级的需求。这篇教程将为您详细介绍如何使用U盘来安装XP纯净版系统。XP纯…

代码随想录Day 17 | 110 平衡二叉树 257 二叉树的所有路径 404 左叶子之和

代码随想录Day 17 | 110 平衡二叉树 257 二叉树的所有路径 404 左叶子之和 平衡二叉树二叉树的所有路径左叶子之和 平衡二叉树 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a; 后序遍历求高度&#xff0c;高度判断是否平衡 | LeetCode&#xff1a;110.平衡二叉树 状态 …

DEJA_VU3D - Cesium功能集 之 117-雷达扫描(圆环效果)

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小140个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码…

C++知识点总结(13):函数

一、定义 函数&#xff0c;指可以实现某个功能&#xff0c;可以重复使用的一段代码。不同的函数之间相互独立&#xff0c;即函数之间的功能互不影响&#xff08;互相的代码&#xff09;。 二、结构 1. 定义 返回值类型 函数名(形参1, 形参2, 形参3...形参n) {...return 值; }2…

Java初学习

Java代码示例&#xff1a; public class helloworld {public static void main(String[] args){System.out.println("hello world");} } Java程序的名字需要和文件名字一致&#xff0c;就是那个helloworld Java程序需要对类有深度的认识&#xff1a; 对象是类的…

2023年全国职业院校技能大赛软件测试赛题—单元测试卷②

单元测试 一、任务要求 题目1&#xff1a;任意输入2个正整数值分别存入x、y中&#xff0c;据此完成下述分析&#xff1a;若x≤0或y≤0&#xff0c;则提示&#xff1a;“输入不符合要求。”&#xff1b;若2值相同&#xff0c;则提示“可以构建圆形或正方形”&#xff1b;若2<…

ipad协议逆向分析实战篇-1

请使用dnspy环境进行学习研究&#xff0c;切勿用于非法操作 1.首先拿到得到的部署包进行逆向分析 2.解压部署包并找到bin这个文件夹 3.找到Wechat.Api.dll这个文件 4.这两个是协议的核心文件&#xff0c;破解了这个核心文件就可以得出逻辑源码 5.首先把Wechat.Api.dll这个…

Pandas实战100例 | 案例 23: 处理空值

案例 23: 处理空值 知识点讲解 处理空值是数据清洗过程中的一个关键步骤。Pandas 提供了多种方法来检测、填充和删除空值。 检测空值: 使用 isnull 方法可以检测 DataFrame 中的空值。填充空值: 使用 fillna 方法可以填充空值。删除包含空值的行或列: 使用 dropna 方法可以删…

C++ (MFC) 单程序运行(防止多开程序)

C (MFC) 单程序运行&#xff08;防止多开程序) 项目文件名:MFCAppTest 在 C*****App.cpp 文件中 CMFCAppTestApp::InitInstance 函数中 添加以下代码 //避免程序的多开 xxxx为信号量的名字 可随意CreateMutex(NULL, TRUE, TEXT("MFCAppTest")); if (GetLastError…

oracle—IMU机制

正常的情况下&#xff0c;当事务需要回滚块的时候&#xff0c;是去undo表空间找 现在是在sharepool中分一个IMUbuffer&#xff0c;将所有的回滚信息写入。直接就可以从中取。减少了物理IO 同时这个过程也产生redo&#xff0c;直接就是图中红色的&#xff0c;不防止崩溃 优点 1…

开机自启动android app

Android App开机自启动_android 开机自启动-CSDN博客 注意权限问题&#xff1a; 第二种实现方式&#xff1a;系统桌面应用 问&#xff1a;android的系统桌面应用启动是什么&#xff1a; 答&#xff1a; Android 系统桌面应用是指用户在设备主屏幕上看到的默认启动界面&…

代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点面试题 02.07. 链表相交、142.环形链表II

文档讲解&#xff1a;虚拟头节点&#xff0c;三指针&#xff0c;快慢指针&#xff0c;链表相交&#xff0c;环形链表&#xff0c; 技巧&#xff1a; 1、对于指针的操作要画图&#xff0c;明确步骤后好做了 2、使用虚拟头节点可以避免对头节点单独讨论&#xff0c;且方便对头节点…

C++ Primer 6.1 函数基础

函数的形参列表 int func(int v,int v2) {int v,v2;//&#xff01;错误 } 函数返回类型 不能是数组和函数&#xff08;两者都不接受对拷&#xff09;&#xff0c;但可以是指针 局部对象 形参和函数体内部的变量称为局部变量&#xff0c;仅在函数内部可见&#xff0c;隐藏外部…