线程安全--互斥锁

在这里插入图片描述

文章目录

  • 一.线程安全问题
      • 读取无效(脏)数据
      • 丢失更新
      • 线程安全的保证--操作的原子性
  • 二.互斥锁及其实现原理
    • 互斥锁的实现原理
    • 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.hqwc.cn/news/345757.html

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

相关文章

【Databend】行列转化:一行变多行和简单分列

文章目录 数据准备和需求生成序列和分隔函数根据分隔符变多行JSON 数据简单分列总结 数据准备和需求 行列转化在实际工作中很常见&#xff0c;其中最常见的有一行变多行&#xff0c;有下面一份数据&#xff1a; drop table if exists fact_suject_data; create table if not …

设计模式之迭代器模式【行为型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

linux搭建SRS服务器

linux搭建SRS服务器 文章目录 linux搭建SRS服务器SRS说明实验说明搭建步骤推流步骤查看web端服务器拉流步骤final SRS说明 SRS&#xff08;simple Rtmp Server&#xff09;,是一个简单高效的实时视频服务器&#xff0c;支持RTMP/WebRTC/HLS/HTTP-FLV/SRT, 是国人自己开发的一款…

“所有伙食开销统计:轻松查看,智能管理你的餐饮支出“

你是否经常为伙食开销感到困扰&#xff0c;不知道如何有效控制和管理&#xff1f;现在&#xff0c;有了我们的伙食开销统计工具&#xff0c;这些问题将得到轻松解决&#xff01; 首先第一步&#xff0c;我们要进入晨曦记账本并在上方功能栏里选择“查看方式”。并在弹出来的列表…

c++算法之枚举

目录 解空间的类型 循环枚举解空间 例题 特别数的和 输入格式 输出格式 输入样例&#xff1a; 输出样例&#xff1a; 解 例题 反倍数 问题描述 输入格式 输出格式 样例输入 样例输出 解 例题 找到最多的数 解 枚举算法是一种基本的算法思想&#xff0c;它通过…

写一个文字滑动切换样式效果

<template><div class"mainrouter centerWindi"><h1>2024,马上暴富</h1></div> </template> <style lang"scss"> /* 居中 */ .centerWindi {apply flex justify-center items-center;text-align: center; }/* 路…

【Linux笔记】进程等待与程序替换

一、进程的终止 1、进程退出码 在讲解进程的终止之前&#xff0c;先要普及一下进程的退出码概念。 我们父进程之所以要创建子进程&#xff0c;就是为了让子进程运行不一样的任务&#xff0c;那么对于子进程执行的这个任务执行完毕后的结果是否正确或者是否出差错&#xff0c…

宝塔面板安装MySQL8数据库

第一步&#xff1a;搜索mysql 第二步: 点击安装 我这里选择安装8版本 第三步&#xff1a;给宝塔配置mysql防火墙 第四步&#xff1a;修改数据库密码 第五步&#xff1a;想要使用navicat连接 需要修改root的权限 &#xff08;1&#xff09;使用secureCRT先登录mysql (2) 输入u…

网络——华为与华三

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 写在前面 大家好&#xff0c;我是网络豆&#xff0…

公司运营数据分析大屏:引领企业决策,驱动业务增长

在数字化时代&#xff0c;数据已经成为企业决策的关键。为了更好地洞察市场趋势、优化业务流程、提升运营效率&#xff0c;越来越多的企业开始引入数据分析大屏以分析公司运营状况。这一创新举措不仅改变了传统的管理模式&#xff0c;更引领企业迈向智能化决策的新篇章。 公司运…

Linux常用命令之tar解压缩文件、uname -a查看系统信息

tar&#xff1a;解压缩 *.Z-------------------compress程序压缩的文件*.gz------------------gzip程序压缩的文件*.bz2-----------------bzip2程序压缩的文件*.tar------------------tar程序打包的数据,并没有压缩*.tar.gz---------------tar程序打包的文件,其中经过gzip的压…

PHP代码审计基础知识

前言 本文章主要是PHP代码审计的一些基础知识&#xff0c;包括函数的用法&#xff0c;漏洞点&#xff0c;偏向基础部分&#xff0c;个人能力有限&#xff0c;部分可能会出现错误或者遗漏&#xff0c;读者可自行补充。 代码执行 代码执行是代码审计当中较为严重的漏洞&…