Linux多线程之线程互斥

news2024/9/30 17:32:59

(。・∀・)ノ゙嗨!你好这里是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.coloradmin.cn/o/1505936.html

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

相关文章

Unity 使用HyBirdCLR调用Newtonsoft.json报错问题

查了老半天&#xff0c;原来是这里的问题 官方解释 解释&#xff1a; 在Unity的IL2CPP Code Generation中&#xff0c;"Faster runtime"和"Faster (smaller) builds"是两种不同的优化设置选项&#xff0c;它们分别影响着运行时性能和构建大小。下面是它们…

一款Mac系统NTFS磁盘读写软件Tuxera NTFS 2023 for Mac

当您获得一台新 Mac 时&#xff0c;它只能读取 Windows NTFS 格式的 USB 驱动器。要将文件添加、保存或写入您的 Mac&#xff0c;您需要一个附加的 NTFS 驱动程序。Tuxera 的 Microsoft NTFS for Mac 2023是一款易于使用的软件&#xff0c;可以在 Mac 上打开、编辑、复制、移动…

MacBook2024苹果免费mac电脑清理垃圾软件CleanMyMac X

CleanMyMac X是一款专业的Mac清理软件&#xff0c;具备多种强大功能。首先&#xff0c;它能够智能清理Mac磁盘上的垃圾文件和多余语言安装包&#xff0c;从而快速释放电脑内存。其次&#xff0c;CleanMyMac X可以轻松管理和升级Mac上的应用&#xff0c;同时强力卸载恶意软件并修…

机器学习中的经典算法总结

经典算法 有监督算法逻辑回归支持向量机SVM决策树朴素贝叶斯K近邻&#xff08;KNN&#xff09; 无监督算法K-meansPCA主成分分析预留模版 有监督算法 逻辑回归 简介 逻辑回归是机器学习中一种经典的分类算法&#xff0c;通常用于二分类任务&#xff0c;基本思想是构建一个线性…

修改Android打包apk的名字和目录

app打包生成apk后通常需要进行备份&#xff0c;但是要区分好哪个apk是什么版本的、什么时候打包的&#xff0c;以方便以后区分使用。 最开始的想法是把版本号、创建时间这些加在apk文件名上即可&#xff0c;但是公司要求apk使用一个固定的名称&#xff0c;那我怎么保存版本号信…

04-多核多cluster多系统之间缓存一致性概述

引流关键词:缓存,高速缓存,cache, CCI,CMN,CCI-550,CCI-500,DSU,SCU,L1,L2,L3,system cache, Non-cacheable,Cacheable, non-shareable,inner-shareable,outer-shareable, optee、ATF、TF-A、Trustzone、optee3.14、MMU、VMSA、cache、TLB、arm、armv8、armv9、TEE、安全、内存…

ABA关键词选品,大卖成功打造亚马逊爆款的秘密武器

做亚马逊新手在产品方面容易出现的问题&#xff08;都说跨境7分靠选品&#xff0c;3分靠运营&#xff0c;如果品没选对&#xff0c;直接掉坑里&#xff09;&#xff1a; 比较盲目的上产品&#xff0c;没有进行详细的市场调研&#xff08;如目标市场&#xff0c;国情以及受众分析…

typescript简介和类型以及编译和打包配置以及类与对象的介绍

介绍ts JS 的超集 javascript 轻量级的变量约束 支持在任何支持JavaScript的平台执行 添加很多类型 提高大型项目的可维护性 最终会编译成JS。类似于预编译的处理类似于less,scss 搭建开发环境 1.下载Node.js 64位: https://nodejs.org/dist/v14.15.1/node-v14.15.1-x64 32位…

C++笔记之嵌套类中的成员函数识别外层类的成员变量

C++笔记之嵌套类中的成员函数识别外层类的成员变量 —— 杭州 2024-03-10 code review! 文章目录 C++笔记之嵌套类中的成员函数识别外层类的成员变量1.嵌套类声明完之后跟一个标识符是什么含义?2.嵌套类中的成员函数如何识别外层类的成员变量?1.嵌套类声明完之后跟一个标识…

2024蓝桥杯每日一题(双指针)

一、第一题&#xff1a;牛的学术圈 解题思路&#xff1a;双指针贪心 仔细思考可以知道&#xff0c;写一篇综述最多在原来的H指数的基础上1&#xff0c;所以基本方法可以是先求出原始的H指数&#xff0c;然后分类讨论怎么样提升H指数。 【Python程序代码】 n,l map(int,…

java中使用rabbitmq

文章目录 前言一、引入和配置1.引入2.配置 二、使用1.队列2.发布/订阅2.1 fanout(广播)2.2 direct(Routing/路由)2.3 Topics(主题)2.4 Headers 总结 前言 mq常用于业务解耦、流量削峰和异步通信,rabbitmq是使用范围较广,比较稳定的一款开源产品,接下来我们使用springboot的sta…

第十五届蓝桥杯模拟赛(第三期)

大家好&#xff0c;我是晴天学长&#xff0c;本次分享&#xff0c;制作不易&#xff0c;本次题解只用于学习用途&#xff0c;如果有考试需要的小伙伴请考完试再来看题解进行学习&#xff0c;需要的小伙伴可以点赞关注评论一波哦&#xff01;蓝桥杯省赛就要开始了&#xff0c;祝…

【谈一谈】并发_Synchronized

Synchronized 又到周末了,最近的话(有点子小日子不好过,哈哈哈!~)但是,我还是报之以歌哈哈哈 本次写关于并发_Synchronized的优化以及底层实现原理 说说心里话~其实是非常的累,原因应该怎么说呢?我发现自己在如今的这家公司,我处于一种活多钱少以及关键现在给的或自己不想干,因…

【HarmonyOS】ArkTS-联合类型

目录 联合类型实例 联合类型 联合类型是一种灵活的数据类型&#xff0c;它修饰的变量可以存储不同类型的数据。 语法&#xff1a;let 变量: 类型1 | 类型2 | 类型3 值 基于联合类型&#xff0c;变量可存不同类型数据 实例 // 需求&#xff1a;定义一个变量&#xff0c;存放…

C语言---单身狗问题

1.单身狗初阶 这个题目就是数组里面有一串数字&#xff0c;都是成对存在的&#xff0c;只有一个数字只出现了一次&#xff0c;请你找出来 &#xff08;1&#xff09;异或是满足交换律的&#xff0c;两个相同的数字异或之后是0&#xff1b; &#xff08;2&#xff09;让0和每个…

JDBC和连接池

JDBC和连接池 大纲 JDBC连接数据库的方式 具体案例 JDBC 需求&#xff1a;满足Java程序能对多个不同的数据库进行操作&#xff0c;而创建了一种接口&#xff0c;实现对数据库的规范 连接数据库的方式 1.方法1 先创建一个Driver对象&#xff0c;然后设置连接到的数据…

操作系统常见问题

操作系统常见问题 调度相关调度算法进程、线程、协程 同步相关进程间通信方式死锁&#xff08;deadlocks&#xff09;是指两个或多个进程在等待对方释放资源时发生的一种状态。操作系统原子操作多线程锁 内存相关虚拟内存页表用户空间分布线程切换上下文线程拥有哪些资源栈中主…

双向数据绑定:Vue.js的魔法背后

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

python单例模式应用之pymongo连接

文章目录 单例模式介绍模块简介安装简单的连接使用单例模式的连接单例类的实现配置的使用单例模式的测试 单例连接的调用 https://gitee.com/allen-huang/python 单例模式介绍 适用场景&#xff1a; 单例模式只允许创建一个对象&#xff0c;因此节省内存&#xff0c;加快对象访…

网页设计中通过css在一个固定宽度的div容器中让一行超出的文本隐藏并省略掉

实现效果&#xff1a; 实现的关键css&#xff1a; overflow&#xff1a;hidden&#xff1b;这个表示超出容器的内容进行隐藏 white-space&#xff1a;nowrap&#xff1b;表示文本不断行显示 text-overflow&#xff1a;ellipsis&#xff1b;表示超出的部分用省略号进行表示 …