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,一经查实,立即删除!

相关文章

探索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;来…

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 获取非…

APP2:android studio如何使用lombok

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

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

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

使用Vue.js开发前端项目

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

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

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

软件测试 需求

文章目录 1. 需求1.1 什么是需求1.2 为什么要有需求1.3 测试人员眼中的需求1.4 如何深入理解需求 2. 测试用例的概念2.1 什么是测试用例2.2 为什么要有测试用例 3. 软件错误&#xff08;BUG&#xff09;的概念4. 开发模型和测试模型4.1 软件的生命周期4.2 瀑布模型&#xff08;…

【深度学习笔记】6_10 双向循环神经网络bi-rnn

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.10 双向循环神经网络 之前介绍的循环神经网络模型都是假设当前时间步是由前面的较早时间步的序列决定的&#xff0c;因此它们都将信…

STM32---IIC通信协议(含源码,小白进)

写在前面&#xff1a;在前面的学习过程中&#xff0c;我们学习了串口通信的USART&#xff08;通用同步异步收发器&#xff09;&#xff0c;本节我们将继续学习一种串行通信协议——IIC通信协议。之前我使用51单片机也分享过相关的IIC通信的知识&#xff0c;其实本质的知识是相通…

去哪里找视频素材?这几个视频素材资源网您看看

朋友们&#xff0c;是不是在抖音短视频的创作过程中感觉自己的视频素材库就像饭后的草莓派&#xff0c;美好但不够充实&#xff1f;别担心&#xff0c;我这就给你们送上几个超级赞的素材网站&#xff0c;保证让你的创作素材库瞬间丰富起来 1&#xff0c;蛙学府 这个网站简直就…

Delphi 的Read 与Readln 的区别

结合运行窗口&#xff0c;你输入1 2 3 4 这是一行ReadLn在读入时把这四个数当成一行&#xff0c;read(a,b)只读入了前两个数&#xff1a;1 2&#xff0c;就准备读下一行了&#xff0c;下一行输入3&#xff0c;再下一行输入2&#xff0c;所以输出1232&#xff1b; Read是逐个读…

Python列表及其操作详解,从此不再迷茫!

在前面的文章中&#xff0c;我们详细讲了六大数据类型中的数字类型&#xff0c;字符串类型。相信大家都能够熟练的掌握了。那么今天我们来讲解列表&#xff08;list&#xff09;。 这是一种常用且重要的数据类型&#xff0c;List可以用来存储一系列的元素&#xff0c;对于后期…

怎么看待Groq

用眼睛看。 就是字面上的意思用眼睛看。 我属于第一波玩到的,先给大家一个直观的印象,Groq到底有多快。 目前Groq只能选Llama的70b,和Mixtral的MoE,那我选7*8的这个MoE模型来实验。 这么好些字大概花了不到1秒,流式响应,其实是不是流式已经没那么重要了 ,然后看每秒Toke…

MongoDB官网查看 MongoClient 驱动API 文档的详细步骤

目录 MongoDB官网查看 MongoClient 驱动API 文档的详细步骤1、先进入[mongodb的官网](https://www.mongodb.com/zh-cn)&#xff0c;点击【服务器文档】2、点击这个 [MongoDB Documentation](https://www.mongodb.com/docs/) 文档。3、然后点开【Java】的驱动文档4、先查看同步的…

【数据结构】详解时间复杂度和空间复杂度的计算

一、时间复杂度&#xff08;执行的次数&#xff09; 1.1时间复杂度的概念 1.2时间复杂度的表示方法 1.3算法复杂度的几种情况 1.4简单时间复杂度的计算 例一 例二 例三 1.5复杂时间复杂度的计算 例一&#xff1a;未优化冒泡排序时间复杂度 例二&#xff1a;经过优化…

【海贼王的数据航海:利用数据结构成为数据海洋的霸主】探究二叉树的奥秘

目录 1 -> 树的概念及结构 1.1 -> 树的概念 1.2 -> 树的相关概念 1.3 -> 树的表示 1.4 -> 树在实际中的运用(表示文件系统的目录树结构) 2 -> 二叉树概念及结构 2.1 -> 二叉树的概念 2.2 -> 现实中的二叉树 2.3 -> 特殊的二叉树 2.4 ->…