013 Linux_互斥

news2025/6/18 17:35:08

前言

本文将会向你介绍互斥的概念,如何加锁与解锁,互斥锁的底层原理是什么

线程ID及其地址空间布局

在这里插入图片描述

每个线程拥有独立的线程上下文:一个唯一的整数线程ID, 独立的栈和栈指针,程序计数器,通用的寄存器和条件码。
和其他线程共享的进程上下文的剩余部分:整个用户虚拟地址空间,那就是上图的数据段,堆以及所有的共享库代码和数据区域,也共享所有打开文件的集合。
pthread_create函数会产生一个线程id,存放到第一个参数指向的地址中,如果你将这个id打印出来会发现特别大,其实这串数字是一个地址,这个地址就是一个虚拟地址,这样在主线程产生的临时数据都压在系统栈上,而其他线程则存储在pthread库提供的栈内。
这里要注意的是线程的寄存器的内容是不共享的,通常栈区是被相应线程独立访问的,但是还是可能出现一个线程去访问另一个线程中的栈区的情况。如果这个线程获得了指向另一个线程栈区的指针,那么它就可以读写这个栈的任何部分。

互斥相关概念

临界资源:多线程执行流共享的资源就叫做临界资源 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
互斥量: 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个 线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之 间的交互。
多个线程并发的操作共享变量,会带来一些问题。

模拟抢票

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>
using namespace std;
#define NUM 4
class threadData
{
public:
    //构造函数
    threadData(int number)
    {
        threadname = "thread-" + to_string(number);
    }
public:
    string threadname;      //线程名
};
int tickets = 1000;
void* getTicket(void *args)
{
    //安全类型转换
    threadData *td = static_cast<threadData *>(args);
    const char *name = td->threadname.c_str();
    while(tickets)
    {
        if(tickets > 0)
        {     
            usleep(10000);
            printf("who=%s, get a ticket: %d\n", name, tickets);
            tickets--; 
 
        }
        else break;
    }
    printf("%s ... quit\n", name);
    return nullptr;
}
 int main()
{
    vector<pthread_t> tids;
    vector<threadData *> thread_datas;
    for(int i = 1; i <= NUM; i++)
    {
        pthread_t tid;
        //构造一个对象指针
        threadData *td = new threadData(i);
        //存放对象指针
        thread_datas.push_back(td);
        //创建线程,将对象指针作为参数传递给getTicket
        pthread_create(&tid, nullptr, getTicket, thread_datas[i-1]);
        //管理线程id
        tids.push_back(tid);
    }
    //线程等待
    for(auto thread : tids)
    {
        pthread_join(thread, nullptr);
    }
    for(auto td : thread_datas)
    {
        delete td;
    }
    return 0;
}

在这里插入图片描述

现象:明明存在对tickets的条件判断,可是票数依旧被抢到了负数

在这里插入图片描述

原因是:多个线程可能同时检查tickets大于0,然后同时减少tickets的值。这可能导致tickets的值减少到负数,因为每个线程都可以执行tickets–操作,即使tickets已经为0。
从底层来看:
Ticket–这一步骤在汇编上是三条代码

1、先将tickets读入到cpu的寄存器中
2、cpu内部进行–操作
3、将计算结果的数据写回内存

举个例子
在这里插入图片描述
引入一个概念:线程在执行的时候,将共享数据,加载到CPU寄存器的本质:把数据的内容,变成了自己的上下文—以拷贝的方式,给自己单独拿了一份(也就是说会将数据保存到自己的上下文当中)

1、当执行线程一的时候,倘若线程一刚执行完第一步就被切走
2、假设线程2一直能执行完3个步骤,重复执行,没有被中断,最终票数被减到10了,当刚要执行第一步骤的时候此时被切换,此时将减到10的数据保存到自己的上下文当中
3、切换到线程一,并不是紧接着执行第二步,而是恢复自己的上下文数据1000到CPU当中,它认为数据是1000,最后将计算出来的999写回内存,此时线程二先前将票数减到10的工作白做了
4、而此时如果再次切换到线程二,线程二再将内存中的tickets读取到寄存器当中(票数就又变成了999),因此可以看出:多个线程并发的操作共享变量,会带来一些问题,是线程切换导致的
我们也可以看出无论是–还是++其实都不是原子性的,因为它们会在cpu调度是被打断。

互斥锁

为了解决上述抢票的问题(共享数据被读哦现场并发访问造成数据不一致的问题)
需要做到三点:

代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
在这里插入图片描述

加锁:模拟抢票

引入函数接口

互斥锁
方法1,静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法2,动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);
参数:
mutex:要初始化的互斥量
attr:NULL

销毁互斥量需要注意:
使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误码

调用 pthread_ lock 时,可能会遇到以下情况: 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>
using namespace std;
#define NUM 4

class threadData
{
public:
    //构造函数
    threadData(int number, pthread_mutex_t *mutex)
    {
        threadname = "thread-" + to_string(number);
        lock = mutex;
    }
public:
    string threadname;      //线程名
    pthread_mutex_t *lock;
};
int tickets = 500;
void* getTicket(void *args)
{
    //安全类型转换
    threadData *td = static_cast<threadData *>(args);
    const char *name = td->threadname.c_str();
    while(true)
    {
   		//上锁
        pthread_mutex_lock(td->lock); 
        if(tickets > 0)
        {        
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets);
            tickets--; 
            pthread_mutex_unlock(td->lock);
        }
        else
        {
        	//解锁
            pthread_mutex_unlock(td->lock);
            break;
        }
    }
    printf("%s ... quit\n", name);
    return nullptr;
}
 int main()
{
    vector<pthread_t> tids;
    vector<threadData *> thread_datas;
    pthread_mutex_t lock;
    //初始化锁
    pthread_mutex_init(&lock, nullptr);
    for(int i = 1; i <= NUM; i++)
    {
        pthread_t tid;
        //构造一个对象指针
        threadData *td = new threadData(i, &lock);
        //存放对象指针
        thread_datas.push_back(td);
        //创建线程,将对象指针作为参数传递给getTicket
        pthread_create(&tid, nullptr, getTicket, thread_datas[i-1]);
        //管理线程id
        tids.push_back(tid);
    }
    //线程等待
    for(auto thread : tids)
    {
        pthread_join(thread, nullptr);
    }
    for(auto td : thread_datas)
    {
        delete td;
    }
    return 0;
}

现象:上锁了,但是票数都是由一个线程抢走了
在这里插入图片描述
为什么会有这样的现象呢?
故事时间

在纯互斥环境里,如果锁分配不够合理,容易导致其它线程的饥饿问题
比如:存在一个独立自习室,规矩:出去后,必须把钥匙放到指定的位置。倘若自习室里的人需要去吃饭,然后出门,看到一大堆人在等他出来,然后他又进去了,因为他不想失去这把🔑,但是由于他距离门更近一些,因此他对钥匙的竞争更强一些
我们应该对此再加一个规矩:出来的人,不能立马重新申请锁,想要继续申请,必须排到队列的最后面,外面来的,必须排队

了解上述的故事之后,我们可以对代码进行如下修改:
在这里插入图片描述

在这里插入图片描述

互斥量实现原理探究

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下
在这里插入图片描述
1、当线程切换时,线程要把它的上下文数据带走(即把在寄存器中的值拷贝一份),还要记录执行到哪一个位置了
2、当线程一执行完第一步就被切换,首先把0保存到自己的上下文当中,回来的时候要执行xchgb
3、 线程二来了:把0mov到寄存器里,让后与内存中的mutex=1作交换(此时内存中的值为0,cpu寄存器的值为1)当正要做判断的时候,被切换了,线程二要把寄存器中的内容带走,并记录即将执行if语句
4、线程一回来了:首先要恢复上下文数据,将0又恢复到寄存器里,然后执行交换,发现 跟内存交换完后,依旧是0
5、原因是线程二已经拿走了1,线程一申请锁失败,不会被调度,线程二再恢复寄存器中的数据,继续进行判断大于0,申请锁成功
锁本身就是共享资源,放在内存里这个数据(仅有一把锁)就是被所有线程共享的

在这里插入图片描述
此时持有锁的线程再将mutex中的值(此时为0)与1交换
在这里插入图片描述

小结

今日的分享就到这里啦,如果本文存在疏漏或错误的地方,还请您能够指出!
在这里插入图片描述

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

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

相关文章

基于springboot实现大学外卖管理系统项目【项目源码+论文说明】

基于springboot实现大学外卖管理系统演示 摘要 如今&#xff0c;信息化不断的高速发展&#xff0c;社会也跟着不断进步&#xff0c;现今的社会&#xff0c;各种工作都离不开信息化技术&#xff0c;更离不开电脑的管理。信息化技术也越来越渗透到各小型的企业和公司中&#xff…

pytorch(九)卷积神经网络

文章目录 卷积神经网络全连接神经网络与卷积神经网络的区别概念性知识mnist数据集(卷积神经网络) GoogLeNetInception 残差网络ResNet残差块结构 卷积神经网络 全连接神经网络与卷积神经网络的区别 全连接神经网络是一种最为基础的前馈神经网络&#xff0c;他的每一个神经元都…

AndroidStudio跑马灯实现

在activity_main.xml中编写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_h…

【考研数学】打基础,张宇《30讲》还是武忠祥《基础篇》?

张宇的30讲还是不太适合零基础的考研党去听...因为宇哥整体节奏较快&#xff0c;如果目标分较高&#xff0c;有一定的基础还是建议的 身边真的很多130-140的大佬都是跟着张宇从头到尾&#xff0c;张宇老师的习题册非常适合基础扎实&#xff0c;想冲刺高分的考研党 我是属于基…

R语言的数据类型与数据结构:向量、列表、矩阵、数据框及操作方法

R语言的数据类型与数据结构&#xff1a;向量、列表、矩阵、数据框及操作方法 介绍向量列表矩阵数据框 介绍 R语言拥有丰富的数据类型和数据结构&#xff0c;以满足各类数据处理和分析的需求。本文将分享R语言中的数据类型&#xff0c;包括向量、列表、矩阵、数据框等&#xff…

Skywalking官方的实战模拟项目Live-Demo

Skywalking 官方的实战模拟项目Live-Demo Live-Demo 是 Skywalking 官方的实战模拟项目&#xff0c;其中包含4个子模块项目 projectA访问projectB、projectC两个SpringBoot项目 projectB访问本地的H2数据库 projectC访问www.baidu.com并同时向一台Kafka消息队列写入数据 proje…

VSCODE解决git合并过程中的冲突问题;error: failed to push some refs to

1&#xff1a;异常现象 推送有冲突的git修改到远端的时候&#xff0c;会有如下提示 git.exe push --progress “origin” master:master To http://gitlab.xxx.com/dujunqiu/test.git ! [rejected] master -> master (fetch first) error: failed to push some refs to ‘…

解决“ModuleNotFoundError: No module named ‘RPi._GPIO‘”

背景描述 树莓派4B Ubuntu20.04 Python3.9安装RPi.GPIO后无法使用 解决办法 使用sudo pip3 install RPi.GPIO --upgrade尝试更新&#xff0c;发生红字报错&#xff0c;提示在编译过程中缺少Python开发头文件&#xff08;Python.h&#xff09;&#xff0c;因此应该先安装他&…

短视频矩阵系统--抖去推---年后技术还能迭代更新开发运营吗?

短视频矩阵系统#短视频矩阵系统已经开发3年&#xff0c;年后这个市场还能继续搞吗&#xff1f;目前市面上开发短视频账号矩阵系统的源头公司已经不多了吧&#xff0c;或者说都已经被市场被官方平台的政策影响的不做了吧&#xff0c;做了3年多的矩阵系统开发到现在真的是心里没有…

excel 动态列导出

excel动态列&#xff0c;只好用poi来写了&#xff0c;也并不复杂&#xff0c;一样就这个件事情抽像为几步&#xff0c;就是套路了&#xff0c;开发效率就上去了。 1 准备空模板 导出操作与excel模板的导出一样&#xff0c;可以参考excel导出标准化 2 自定义SheetWriteHandler …

TI IWR6843ISK ROS驱动程序搭建

1、设备准备 1.1 硬件设备 1&#xff09;TI IWR 6843 ISK 1块 2&#xff09;Micro USB 数据线 1条 1.2 系统环境 1&#xff09;VMware Workstation 15 Player 虚拟机 2&#xff09;Ubuntu18.04 并安装有 ROS1 系统 如若没有安装 ROS 系统&#xff0c;可通过如下指令进行…

HTML 02

1.列表 布局内容排列整齐的区域 (1)无序列表 写法&#xff1a; <ul><li>列表条目1</li><li>列表条目2</li><li>列表条目3</li></ul> 浏览器中显示&#xff1a; 注意&#xff1a; ul标签里只能包裹li标签 li标签里可以包…

算法学习系列(四十):贡献法

目录 引言概念一、孤独的照片二、牛的基因学三、字串分值 引言 关于这个贡献法考的不是很多&#xff0c;主要题型是出现在需要枚举每一个组合这类题&#xff0c;出现的次数较多。没有固定的模板&#xff0c;就是一种思想&#xff0c;跟贪心一样&#xff0c;每个题都是不一样的…

探讨系统测试的最佳实践与思维模式!

这是测试活动过程详解系列的最后一篇文章。之前的想法&#xff0c;是对测试过程各重要环节进行拆解&#xff0c;然后介绍这个环节重点要做的事情&#xff0c;为什么要做这些事&#xff0c;以及注意事项。 前面几篇文章分别介绍了单元测试、集成测试、回归测试阶段要解决的问题…

yolo模型中神经节点Mul与Sigmoid 和 Conv、Concat、Add、Resize、Reshape、Transpose、Split

yolo模型中神经节点Mul与Sigmoid 和 Conv、Concat、Add、Resize、Reshape、Transpose、Split 在YOLO&#xff08;You Only Look Once&#xff09;模型中&#xff0c;具体作用和用途的解释&#xff1a;

qt 日志 格式化打印 QMessagePattern

进入 qt源码 调试:qt creator debug 无法进入 qt源码 调试-CSDN博客 qt为 格式化打印 日志 提供了一个简易的 pattern(模式/格式) 词法解析类QMessagePattern,该类在qt的专门精心日志操作的源码文件Src\qtbase\src\corelib\global\qlogging.cpp 中 该类直接在构造函数中…

基于springboot+vue的食品安全管理系统(源码+论文)

目录 前言 一、功能设计 二、功能实现 1 首页 2 后台登录 3 食品信息添加页面 4 食品查询 三、库表设计 四、论文 前言 从事食品行业的商家可能会对于食品的储存以及食品的销售&#xff0c;都有着不同门道的想法&#xff0c;那么如何能将这些想法一一实现&#xff0c;…

图像处理ASIC设计方法 笔记8 卷积计算芯片的结构

(一) P81 卷积芯片内部模板框图 该设计有两个数据通路:图像数据和模板数据。 图像数据是经过帧控制、实时图SPRAM(写控制、 SPRAM读控制、数据读控制)、计算单元; 模板数据是经过模板SPRAM、计算单元。 4.5.4运算单元像素寄存器控制 存储SPRAM写入的64bit数据,输出为…

FPGA的时钟资源

目录 简介 Clock Region详解 MRCC和SRCC的区别 BUFGs 时钟资源总结 简介 7系列FPGA的时钟结构图&#xff1a; Clock Region&#xff1a;时钟区域&#xff0c;下图中有6个时钟区域&#xff0c;用不同的颜色加以区分出来 Clock Backbone&#xff1a;从名字也能看出来&#x…

PermissionError: [Errno 13] Permission denied: ‘xview/xView_train.geojson‘

原因 如果你试图打开一个文件&#xff0c;但你的路径是一个文件夹&#xff0c;就会发生这种情况。 判断是否是文件夹 import os path r"my/path/to/file.txt" assert os.path.isfile(path) with open(path, "r") as f:pass或者可以看下该文件是否真没有…