当前位置: 首页 > news >正文

深入了解linux系统—— 线程互斥

互斥

在之前的学习中,了解到了临界资源、临界区、等等。

临界资源:多线程(执行流)共享的资源。

临界区:每一个线程(执行流)内部访问临界资源的代码。

**互斥:简单来说就是在任何时刻保证有且只有一个执行流进入临界区,访问临界资源;**对临界资源起保护作用

原子性:不会被任何调度机制打断的操作;要么完成,要么未完成

互斥量mutex

在了解互斥量之前,先来看以下代码:

#include <iostream>
#include <unistd.h>
int count = 1000; 
void *ticket(void *args)
{// 预定演唱会门票std::string name = static_cast<char *>(args);while (count > 0){std::cout << name << " 售出门票 : " << count << std::endl;count--;usleep(100);}return (void *)100;
}
int main()
{pthread_t t1, t2, t3, t4;pthread_create(&t1, nullptr, ticket, (void *)"pthread-1 ");pthread_create(&t2, nullptr, ticket, (void *)"pthread-2 ");pthread_create(&t3, nullptr, ticket, (void *)"pthread-3 ");pthread_create(&t4, nullptr, ticket, (void *)"pthread-4 ");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);return 0;
}

上述代码,模拟演唱会门票的预定,由4个线程(执行流)同时进行售票;

看一下运行结果:

在这里插入图片描述

可以发现,一共1000张门票,4个线程(执行流)同时进行售票(count--);

在售票的过程中,竟然出现了-1-2

为什么会出现这种情况呢?

很简单,多个执行流访问count,当一个线程执行到count--时,另一个线程已经进入whlile循环,这样count--后已经<0了;

此外,如果线程执行while内代码,还未执行到count--,线程就被切换了;此时其他线程执行count还是满足条件的。

那也就是说,线程在访问count时,会被别的线程打扰,从而导致数据不一致问题。

原子性

此外,对于上述代码还存在一个问题,那就是count--不是原子的。

到这里,可能会感觉很懵,count--不是原子的?

objdump -d a.out > test.objdump
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 
600b34 <count>
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 
600b34 <count>

通过反汇编,可以看一下count--部分的汇编代码,可以看到是分三步进行的:

  • load:将共享变量count从内存加载到寄存器中
  • update:更新寄存器中的值,执行-1操作
  • store:将新值从寄存器中写回共享变量count的内存地址

互斥锁mutex

对于上述的多执行流访问临界资源,导致数据不一致问题;

所以必须有互斥行为:

代码进入临界区执行时,不允许其他线程进入临界区;

简单来说就是:在任意时刻只允许一个线程(执行流)访问临界资源

要做到在任意时刻只有一个执行流访问资源,那就需要一把锁(互斥锁)

在这里插入图片描述

对临界区加锁,在执行临界区代码之前,先申请锁。

  • 如果申请成功,说明当前没有线程访问临界资源,可以继续执行;
  • 申请失败则说明当前有线程正在访问临界资源(执行临界区代码),就要等待。

当执行完临界区代码,就要释放锁,让其他线程可以进行临界区访问临界资源。

Linux上提供的这把锁叫互斥量。

1. 创建(初始化)信号量

创建并初始化信号量有两种方式:动态分配/静态分配;

简单来说就是定义全局变量/局部变量

静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

动态分配:

如果将互斥量定义局部变量,就需要我们手动调用相关接口去初始化和销毁该信号量。的点点滴滴的点点滴滴

初始化互斥量接口函数:pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数:

参数一:指要初始化的互斥量。

参数二:可以设置互斥量的相关属性,nullptr表示默认。

返回值:

初始化成功返回0,失败返回对应的错误码(非0)

2. 销毁互斥量

创建出来的互斥量需要进行销毁:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数和返回值就简单多了:传递要销毁的互斥量;

销毁成功返回0,失败则返回对应错误码(非0

在调用pthread_mutex_destroy时要注意:

  1. 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不能销毁
  2. 不能销毁一个已经加锁的互斥量
  3. 对于要销毁的互斥量,要保证后面不会再被使用

3. 加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

调用pthread_mutex_lock,如果互斥量未被锁定(未锁)该函数将互斥量锁定,然后返回;

如果互斥量处于锁定状态,(或者存在其他线程同时申请互斥量,但是没有竞争到互斥量),那pthread_lock就会陷入阻塞(执行流被挂起)等待互斥量解锁再继续运行。

简单来说就时,调用pthread_mutex_lock申请互斥量,申请成功就返回继续运行;申请失败就阻塞等待。

调用pthread_mutex_unlock,对互斥量解锁。

所以,有了互斥量;我们就可以随上面模拟售票的代码进行加锁;

保证在任意时刻,最多只有一个线程进入临界区访问临界资源:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 1000;
void *ticket(void *args)
{// 预定演唱会门票std::string name = static_cast<char *>(args);while (count > 0){pthread_mutex_lock(&mutex);if (count > 0){std::cout << name << " 售出门票 : " << count << std::endl;count--;}pthread_mutex_unlock(&mutex);usleep(100);}return (void *)100;
}

这里使用PTHREAD_MUTEX_INITIALIZER,我们也可以创建局部互斥量,然后调用pthread_mutex_initphtread_mutex_destroy初始化和销毁互斥量。

创建局部互斥量,这里要让线程访问到同一个互斥量,可以使用全局指针、也可以通过pthread_create创建线程时的参数传递给线程。

互斥量实现原理

我们知道,对于count++它并不是原子的;那互斥量呢?申请互斥量和释放互斥量是原子的吗?是的

为了实现互斥量,绝大部分体系结构都提供了swap或者esxchange指令,该指令可以把寄存器和内存单元的数据交换;因为只有一条指令,就保证了原子性。

那互斥量是如何实现的呢?

在这里插入图片描述

简单来说就是:在某一个寄存器中,存储值1;当线程调用pthread_mutex_lock时,就会交换当前线程互斥量的值和寄存器中的值。

当线程调用pthread_mutex_lock拿到寄存器中的值1,就表示该线程申请锁成功;其他线程再去申请时,寄存器存储的值为0就申请锁失败,就会被挂起等待。

当调用pthread_mutex_unlock时,就会对寄存器写入1;表示释放该互斥量。

互斥量封装

对于互斥量,C++中也存在对应的互斥量类;

这里简单对互斥量进行封装:

class mutex
{mutex(){pthread_mutex_init(&_mutex, nullptr);}~mutex(){pthread_mutex_destroy(&_mutex);}void Lock(){pthread_mutex_lock(&_mutex);}void Unlock(){pthread_mutex_unlock(&_mutex);}private:pthread_mutex_t _mutex;
};

这样在使用时,就可以面向对象式的调用LockUnlock来进行申请锁和释放锁了

此外,我们还可以随mutex再次封装实现自动申请和释放锁。

class lockgroup
{
public:lockgroup(){_mutex.Lock();}~lockgroup(){_mutex.Unlock();}private:mutex _mutex;
};

这样在申请和释放锁时,就不需要显式调用LockUnlock了。

到这里本篇文章内容就结束了,感谢支持

http://www.xdnf.cn/news/1369765.html

相关文章:

  • ArcGIS学习-11 实战-商场选址
  • 洛谷 P12332 题解
  • 如何利用ArcGIS探究环境与生态因子对水体、土壤、大气污染物等影响实践技术
  • pytorch_grad_cam 库学习笔记—— Ablation-CAM 算法的基类 AblationCAM 和 AblationLayer
  • 如何避免频繁切换npm源
  • pytorch-利用letnet5框架深度学习手写数字识别
  • Vue2(七):配置脚手架、render函数、ref属性、props配置项、mixin(混入)、插件、scoped样式
  • 深入解析交换机端口安全:Sticky MAC的工作原理与应用实践
  • 机器视觉学习-day03-灰度化实验-二值化和自适应二值化
  • 【C++】智能指针底层原理:引用计数与资源管理机制
  • 深度学习篇---LeNet-5网络结构
  • 病理软件Cellprofiler使用教程
  • vue2 和 vue3 生命周期的区别
  • 一篇文章拆解Java主流垃圾回收器及其调优方法。
  • LeetCode-22day:多维动态规划
  • 代码随想录Day62:图论(Floyd 算法精讲、A * 算法精讲、最短路算法总结、图论总结)
  • vue2和vue3的对比
  • TensorFlow 深度学习:使用 feature_column 训练心脏病分类模型
  • Day3--HOT100--42. 接雨水,3. 无重复字符的最长子串,438. 找到字符串中所有字母异位词
  • CentOS 7 服务器初始化:从 0 到 1 的安全高效配置指南
  • 肌肉力量训练
  • 木马免杀工具使用
  • 产品经理操作手册(3)——产品需求文档
  • 全链路营销增长引擎闭门会北京站开启倒计时,解码营销破局之道
  • 构建生产级 RAG 系统:从数据处理到智能体(Agent)的全流程深度解析
  • 书生大模型InternLM2:从2.6T数据到200K上下文的开源模型王者
  • word批量修改交叉引用颜色
  • 【SystemUI】新增实体键盘快捷键说明
  • 常用Nginx正则匹配规则
  • ruoyi-vue(十二)——定时任务,缓存监控,服务监控以及系统接口