Linux多线程之线程互斥

(。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~icon-default.png?t=N7T8https://blog.csdn.net/ky233?type=blog

点个关注不迷路⌯'▾'⌯

目录

一、互斥

1.线程间的互斥相关背景概念

2.互斥锁

三、可重入和线程安全

1.概念

2.常见的线程安全和不安全的问题

3.常见的可重入与不可重入情况

4.可重入与线程安全的联系

5.可重入与线程安全的区别

四、死锁

1.死锁的四个必要条件

2.避免死锁的方法


一、互斥

1.线程间的互斥相关背景概念

  • 临界资源:多线程执行流共享的情况下,同一时间只能由一个执行流访问的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界自娱的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

 接下来验证一个问题,如果多线程访问同一个全局变量,并对它进行数据计算,多线程会互相影响吗?

一个看似没问题的多线程抢票程序

void* getTickets(void* args)
{(void)args;while(1){if(tickets>0){usleep(1000);printf("%p:%d\n",pthread_self(),tickets);tickets--;}else{break;}}return nullptr;
}int main()
{pthread_t t1,t2,t3;pthread_create(&t1,nullptr,getTickets,nullptr);pthread_create(&t2,nullptr,getTickets,nullptr);pthread_create(&t3,nullptr,getTickets,nullptr);pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);
}

抢票到最后,不仅仅出现了有两个11张票的情况,甚至还出现了剩余-1张票的情况!所以如果我们不加以保护的话就会引起这种现象

我们用的方式是--,我们都知道--分为三个动作,先在对应的线程把数据读取到CPU中,然后--,最后返回内存,可是如果在还没--的时候cpu进行调度切换了呢?就会导致这个线程认为还有999张票,下一个也是认为999张票,可是下一个线程做完操作了把998返回去了,然后这时候刚刚的线程进来了,那么这个时候就会把之前保存在线程上下文数据中的999返回到CPU中,然后继续没做完的操作并返回998,那么这个时候已经卖出去两张票了,可是剩余的却是998张票!

这样就导致了我们在并发访问的时候,导致了我们数据不一致的问题!

那么要怎么避免这样的问题产生呢!

2.互斥锁

1.用法

pthread_mutex_t mtx;//首先要先定义一把锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//之后对锁进行初始化操作
  • 参数一:开始定义的锁
  • 参数二:初始化时互斥锁的相关属性设置,传递nullptr使用默认的属性
  • 返回值:成功为0,失败返回错误码
  • 如果是全局或者静态的也可以使用这个宏来初始化:PTHREAD_MUTEX_INITIAILZER

 那么如何进行加锁保护呢?

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 参数一:传入刚刚创建的锁
  • 我们在需要串行化的时候加锁就好了

在我们每个线程进入临界区的时候,就会带上一把锁,这个锁在同一时刻只有一个线程可以拿到,其他没有所得线程只能在这里阻塞等待,直到拿到锁的线程解锁之后,并且拿到锁之后才能进入临界区!这就叫做互斥特点

解锁:

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数一:传入刚刚创建的锁

代码演示:

#include <iostream>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <thread>
#include <string.h>
#include <unistd.h>using namespace std;
// 创建锁
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
//临界资源
int tickets = 10000;void *getTickets(void *args)
{(void)args;while (1){// 加锁pthread_mutex_lock(&mtx);//临界区if (tickets > 0){usleep(1000);tickets--;// 解锁pthread_mutex_unlock(&mtx);printf("%s:%d\n", (char*)args, tickets);}else{// 解锁pthread_mutex_unlock(&mtx);break;}}return nullptr;
}int main()
{pthread_t t1, t2, t3;pthread_create(&t1, nullptr, getTickets, (void*)"线程1");pthread_create(&t2, nullptr, getTickets, (void*)"线程2");pthread_create(&t3, nullptr, getTickets, (void*)"线程3");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);
}

加锁的时候一定要保证我们加锁的粒度越小越好!如果是局部的锁,要记得在最后释放

pthread_mutex_destroy(&mtx);

2.加锁之后,线程在临界区是否会被切换呢?会有问题吗?原子性的体现在哪里

答案是会被切换的!但是不会出现上述问题了!虽然我们还是会被切换,但是我们一已经拿到唯一的一把锁了,而且我们又没有释放锁,所以其他线程不能访问临界区!所以也就不能来进入临界区修改资源

原子性体现:在其他线程看来,只有两种情况是有意义的,一种是:没有线程持有锁,这代表的是什么都没做,谁都可以申请锁!第二种:持有锁的线程释放锁了,这也代表的是谁都可以申请锁!所以只有这两种情况队以其他线程来说是有意义的!所以在其他线程来看,当持有锁的线程在进行对应的任务时,这就是原子的!

3.那么加锁就是串行执行了吗?

经过了上面的了解,那么我们对于这个问题也就迎刃而解了,是的,在访问临界区的时候一i当时串行执行的

4.锁本身是否是临界资源呢?

要访问临界资源,每一个线程都必须先看到同一把锁并访问,那么锁本身是否是临界资源呢?那么谁来保证锁的安全呢?

所以为了保证锁的安全,申请和释放锁必须是原子的!!

5.锁是如何实现的

首先先来补充一个背景知识:在执行流视角我们的寄存器的空间是被共享的,但是此村其里面的内容,是被每一个执行流私有的,属于执行流的上下文,切换的时候每个执行流都会带走属于自己的执行流!

第一步:在最开始的时候我们的锁就是个整数比如说是1,其中当我们的指令在执行第一条汇编的时候,把0放到%al寄存器里,这个0是属于我们线程的上下文的

第二步:我们交换把寄存器的值和锁的值相交换,所以寄存器里面的值就变为1,mtx里面的值变为0

第三步:线程来判断寄存器的内容是否是大于0的,如果大于0则返回,代表申请锁成功!如果不大于0,则被挂起等待

在这过程中,是随时都有可能被切换的,如果被切换了,线程1就带着%al中的数据一起被切换了,线程2则要从新开始置0、交换,这个时候mtx里面的数据是0,寄存器里面的数据也是0,交换后还是0,则不满足第三步获取锁的条件,则线程2被阻塞挂起,其他线程也是一样的,直到CPU再次调度回线程1,然后线程1,带着上次带走的数据继续放回寄存器里,然后继续未完成的步骤,直到申请所成功!

也就是说当1被线程1所拿到之后,就相当于这把锁已经被线程1拿到了,而这三部每一步都是原子的,执行每一步的过程中都是不可以被调度的!这样也就相当于获取锁是原子的了!

三、可重入和线程安全

1.概念

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作, 并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们 称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

2.常见的线程安全和不安全的问题

不安全:

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

安全:

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

3.常见的可重入与不可重入情况

不可重入:

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

可重入:

  • 不使用全局变量或静态变量
  • 不使用用malloc或者new开辟出的空间
  • 不调用不可重入函数 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

4.可重入与线程安全的联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

5.可重入与线程安全的区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生 死锁,因此是不可重入的

四、死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

1.死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

2.避免死锁的方法

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

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

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

相关文章

爬虫之矛---JavaScript基石篇2<window对象、Node.js和prototype/constructor的解析(2)>

前言: 继续上一篇https://blog.csdn.net/m0_56758840/article/details/136590411 正文: 1.prototype与constructor 在JavaScript中,原型和构造函数是实现对象扩展和继承的关键概念。它们构成了JavaScript中的原型系统,使我们能够以灵活且高效的方式创建对象和共享属性。 A…

探索CSS预处理器:Sass、Less与Stylus

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Kafka 面试题及答案整理,最新面试题

Kafka中的Producer API是如何工作的&#xff1f; Kafka中的Producer API允许应用程序发布一流的数据到一个或多个Kafka主题。它的工作原理包括&#xff1a; 1、创建Producer实例&#xff1a; 通过配置Producer的各种属性&#xff08;如服务器地址、序列化方式等&#xff09;来…

ArrayList 和 Vector 的区别是什么?

这两个类都实现了 List 接口&#xff08;List 接口继承了 Collection 接口&#xff09;&#xff0c;他们都是有序集合 ①线程安全&#xff1a;Vector 使用了 Synchronized 来实现线程同步&#xff0c;是线程安全的&#xff0c;而 ArrayList 是 非线程安全的。 ②性能&#x…

MySQL执行原理、存储引擎、索引模型简介

1.sql的执行原理 Connectors 连接、支持多种协议&#xff0c;各种语言 Management service 系统管理和控制工具&#xff0c;例如&#xff1a;备份、集群副本管理等 pool 连接池 sql interfaces sql接口-接收命令返回结果 parser 分析解析器&#xff1a;验证 optimizer 优化…

深入浅出计算机网络 day.1 概论② 因特网概述

当你回头看的时候&#xff0c;你会发现自己走了一段&#xff0c;自己都没想到的路 —— 24.3.9 内容概述 01.网络、互连&#xff08;联&#xff09;网与因特网的区别与联系 02.因特网简介 一、网络、互连&#xff08;联&#xff09;网与因特网的区别与联系 1.若干节点和链路互连…

论文:CLIP(Contrastive Language-Image Pretraining)

Learning Transferable Visual Models From Natural Language Supervision 训练阶段 模型架构分为两部分&#xff0c;图像编码器和文本编码器&#xff0c;图像编码器可以是比如 resnet50&#xff0c;然后文本编码器可以是 transformer。 训练数据是网络社交媒体上搜集的图像…

GEE:计算一个遥感影像的空像素占比

作者:CSDN @ _养乐多_ 本文将介绍,如何在 Google Earth Engine (GEE) 平台计算一个遥感影像的空像素占比,其中,包含获取研究区内所有像素的总数的代码,以及获取非空像素的总数的代码。 结果如下图所示, 文章目录 一、核心函数1.1 获取研究区内所有像素的总数1.2 获取非…

使用正则表达式判断是否是数字(适用于更复杂的数字格式)

re.match("^-?\d(\.\d)?$", n) is not None 在Python中&#xff0c;re.match()函数用于从字符串的开始位置匹配正则表达式模式。如果匹配成功&#xff0c;它返回一个匹配对象&#xff1b;如果匹配失败&#xff0c;它返回None。 表达式re.match("^-?\d(\.\d…

Leetcode 3075. Maximize Happiness of Selected Children

Leetcode 3075. Maximize Happiness of Selected Children 1. 解题思路2. 代码实现 题目链接&#xff1a;3075. Maximize Happiness of Selected Children 1. 解题思路 这一题只需要想清楚一个点就行了&#xff1a; 正常情况下&#xff0c;对于确定的n次选择&#xff0c;无…

C++模板扩展

目录 1.非类型模板参数 2.模板的特化 1.非类型模板参数 我们知道&#xff0c;模板一般是不指定类型的&#xff0c;具体是什么类型由其他部分的代码的决定。 模板参数分为类型模板参数和非类型模板参数。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者…

Redis保证数据一致性-延时双删代码实现

Redis和数据库的数据一致性在某些场景下非常重要&#xff0c;如何最大程度保证Reids和数据库之间的数据一致呢&#xff1f; 想必大家第一时间会想到延时双删策略&#xff1a; 在修改数据库之前删除缓存&#xff0c;然后数据库中的数据修改完成后&#xff0c;再过一段时间再删…

APP2:android studio如何使用lombok

一、前言 不知道从哪个版本开始&#xff0c;android studio便无法在plugins中下载lombok了&#xff0c;有人说是内置了&#xff0c;好像有这么回事儿。我主要面临如下两个问题&#xff1a; 使用内置lombok&#xff0c;可以自动生成setter、setter、toString等。但是&#xff0…

mediapipe 实现姿态分析——举手检测

目录 人体姿态检测 效果展示 举手检测 行业应用 代码实现 代码分析 效果展示 代码修改&#xff0c;一只手举起即可 总结 啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦^_^啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦♪(^∇^*)啦啦啦…

踏上Python之旅:从零开始,全面解析Python安装与环境配置的必知必会

在踏入Python编程殿堂之前,首当其冲的任务就是正确安装并配置Python开发环境。本文将引导您掌握Python的安装过程、不同操作系统下的最佳实践,以及如何进行多版本管理和虚拟环境设置,为您的Python开发之路打下坚实基础。 一、Python安装 Windows系统: 访问官网下载: 打开…

休息日的思考与额外题——纯纯暴力day42

文章目录 前言动态规知识点 动规五部曲一、100233. 重新分装苹果二、100247. 幸福值最大化的选择方案总结 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招&#xff0c;计划二刷完卡子哥的刷题计划&#xff0c;加油&#xff01; 二刷决定精刷了&#xff0c;于是参加了卡子…

Linux 自动检测进程是否存活,如果挂掉自动拉起

以 jupyter 服务为例 1、检测脚本 vim /home/ailab/bin/check_ailab.sh #!/bin/bashPID$(ps -ef|grep -v grep|grep "jupyter" |awk {print $2})# 检查进程是否存在 if [ -z "$PID" ]; then# 进程不存在&#xff0c;启动进程echo "Jupyter Noteboo…

使用Vue.js开发前端项目

Vue.js是一个非常受欢迎的渐进式JavaScript框架&#xff0c;用于开发强大而互动的前端应用程序。Vue易于上手&#xff0c;同时拥有强大的功能库和灵活的生态系统。在本篇博客中&#xff0c;我将带你了解使用Vue.js开发项目的基本步骤&#xff0c;并提供相应的代码示例。 环境安…

自由职业者如何在Fiverr兼职赚美金

在这个忙碌的时代&#xff0c;大家都渴望在业余时间找到一份兼职&#xff0c;为自己带来额外的收入。然而&#xff0c;很多人常常感到困惑&#xff0c;不知道如何找到一份既赚钱又不耗费太多时间精力的兼职。今天&#xff0c;我想分享一个新的赚钱平台——Fiverr&#xff0c;让…

qt使用QAxObject操作excel程序关闭之后excel进程未被关闭的解决方案

&#x1f482; 个人主页:pp不会算法^ v ^ &#x1f91f; 版权: 本文由【pp不会算法v】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 今天突然发现电脑变得很卡,我以为内存占用率过高了一看才50%&…