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

[Linux#42][线程] 锁的接口 | 原理 | 封装与运用 | 线程安全

 互斥量 mutex

• 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间 内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

• 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通 过数据的共享,完成线程之间的交互。

• 多个线程并发的操作共享变量,会带来一些问题。

例如想进 单人自习室,要拿钥匙,申请的是同一把锁

1. 锁的接口

定义锁

锁也是一个数据类型,它的类型是pthread_mutex_t

初始化

  1. 静态分配
  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER(不需要销毁)
  1. 动态分配
  • 函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数:

  • mutex:要初始化的互斥量
  • attr:初始化互斥量的属性,一般设置为nullptr即可

返回值:成功返回0,失败返回错误码

对于锁的五个基本使用:

  • 注意:加锁的时候,一定要保证加锁的粒度,越小越好

实操:

//定义pthread_mutex_t mtx;
//初始化
pthread_mutex_init(&mtx, nullptr);
//销毁
pthread_mutex_destroy(&mtx);
//给线程上锁int n = pthread_mutex_lock(td->_pmtx);
//解锁n = pthread_mutex_unlock(td->_pmtx);

示例场景: 

#define THREAD_NUM 5int tickets = 10000;class ThreadData
{
public:ThreadData(const string &name, pthread_mutex_t *pm): _name(name), _pmtx(pm){}
public:string _name;pthread_mutex_t *_pmtx;
};void *GetTickets(void *args)
{ThreadData *td = (ThreadData *)args;while(true){int n = pthread_mutex_lock(td->_pmtx);assert(n == 0);if(tickets > 0){usleep(1000);printf("%s:%d\n", td->_name.c_str(), tickets);tickets--;n = pthread_mutex_unlock(td->_pmtx);assert(n == 0);}else{n = pthread_mutex_unlock(td->_pmtx);assert(n == 0);break;}}delete td;return nullptr;
}int main()
{pthread_mutex_t mtx;pthread_mutex_init(&mtx, nullptr);// 多线程抢票逻辑pthread_t t[THREAD_NUM];for (int i = 0; i < THREAD_NUM; i++){string name = "thread";name += to_string(i + 1);ThreadData *td = new ThreadData(name, &mtx);pthread_create(t + i, nullptr, GetTickets, (void *)td);}for (int i = 0; i < THREAD_NUM; i++){pthread_join(t[i], nullptr);}pthread_mutex_destroy(&mtx);return 0;
}
  • 加锁的本质:是用时间来换取安全
  • 加锁的表现:线程对于临界区代码串行执行
  • 加锁原则:尽量的要保证临界区代码,越少越好

加锁和解锁之间称之为 临界区

 int n = pthread_mutex_lock(td->_pmtx);assert(n == 0);if(tickets > 0){usleep(1000);printf("%s:%d\n", td->_name.c_str(), tickets);tickets--;n = pthread_mutex_unlock(td->_pmtx);

申请锁成功了,才能往后执行,不成功,阻塞等待

  • 线程对于锁的竞争能力可能会不同, 刚执行完,离锁近,所以可能会某一线程一直更容易拿到锁
  • 我们抢到了票,我们会立马抢下一张吗?其实多线程还要执行得到票之后的后续动作。usleep模拟
  • 纯互斥环境,如果锁分配不够合理,容易导致其他线程的饥饿问题!不是说只要有互斥,必有饥饿。适合纯互斥的场景,就用互斥

观察员:

  • 外面来的,必须排队
  • 出来的人,并不能立马重新申请锁,必须排队到队尾

让所有的线程(人 )获取锁,按照一定的顺序。

按照一定的顺序获取资源--同步!

  • 锁本身就是共享资源
  • 所以,申请锁和释放锁本身就是原子的

临界区中,线程可以被切换吗?可以切换

  • 在线程被切出去的时候,是持有锁被切走的。我不在期间,照样没有人能进入临界区访问临界资源
  • 对于其他线程来讲,一个线程要么么有锁,要么释放锁
  • 当前线程访问临界区的过程,对于其他线程是原子的

💡引入新的解决方案,也会伴随着新的问题,在于看重什么,对立与统一 锁添加的智慧


2. 锁的原理

加锁

tickets-- 不是原子的?会变成 3 条汇编语句。原子:一条汇编语句就是原子的

  • 为了实现互斥锁操作,大多数体系结构都提供了 swap 或 exchange 指令,该指令 的作用是把存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使 是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另 一个处理器的交换指令只能等待总线周期。 现在我们把 lock 和 unlock 的伪代码改 一下

三部分的刷入,刷出,制作动图如下

(线程加载到寄存器,与内存实现交换后,带着数据及上下文游走)

交换的本质:把内存中的数据(共享),交换到 CPU 的寄存器中,其实是换到 CPU 此时执行的线程的硬件上下文中,数字 1 (锁)只有一个,随着上下文走了

把一个共享的锁,让一个线程以一条汇编的方式,交换到自己的上下文中--当前线程持有锁了

解锁

将 1 还回去,通过 unlock

💡许多奇奇怪怪的问题,是需要程序员你自己来维护的,这就设计到加锁位置的智慧了


3. 锁的应用--封装

锁的设置:(降低耦合度

class Mutex
{
public:Mutex(pthread_mutex_t *lock):lock_(lock){}void Lock(){pthread_mutex_lock(lock_);}void Unlock(){pthread_mutex_unlock(lock_);}~Mutex(){}
private:pthread_mutex_t *lock_;
};

封装:利用初始化来对锁进行启动

class LockGuard
{
public:LockGuard(pthread_mutex_t *lock):mutex_(lock){mutex_.Lock();}~LockGuard(){mutex_.Unlock();}
private:Mutex mutex_;
};

调用:

#include "LockGuard.hpp"
while (true){{LockGuard lockguard(&lock); // 临时的LockGuard对象, RAII风格的锁!if (tickets > 0){usleep(1000);printf("who=%s, get a ticket: %d\n", name, tickets); // ?tickets--;}elsebreak;}usleep(13); // 我们抢到了票,我们会立马抢下一张吗?其实多线程还要执行得到票之后的后续动作。usleep模拟}

批注:

  1. LockGuard lockguard(&lock); // 临时的LockGuard对象, RAII风格的锁!

while 之后会自动解锁,利用了对象的生命周期

  1. 第二个{ ,明确出了锁的临界区

4. 可重入 VS 线程安全

概念

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

可重入的一般也是线程安全的

常见的线程不安全的情况

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

常见的线程安全的情况

• 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般 来说这些线程是安全的

• 类或者接口对于线程来说都是原子操作

• 多个线程之间的切换不会导致该接口的执行结果存在二义性

常见不可重入的情况

• 调用了 malloc/free 函数,因为 malloc 函数是用全局链表来管理堆的

• 调用了标准 I/O 库函数,标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构,例如 STL

• 可重入函数体内使用了静态的数据结构

常见可重入的情况

• 不使用全局变量或静态变量

• 不使用用 malloc 或者 new 开辟出的空间

• 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

可重入与线程安全联系

• 函数是可重入的,那就是线程安全的

• 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题

• 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别

• 可重入函数是线程安全函数的一种

• 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

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

死锁--代码不会再完后推进了,例如:

 while (true){pthread_mutex_lock(&lock); // 申请锁成功,才能往后执行,不成功,阻塞等待。pthread_mutex_lock(&lock); // 申请锁成功,才能往后执行,不成功,阻塞等待。if(tickets > 0){usleep(1000);...}}

下篇文章将继续讲解~

相关文章:

[Linux#42][线程] 锁的接口 | 原理 | 封装与运用 | 线程安全

互斥量 mutex • 大部分情况&#xff0c;线程使用的数据都是局部变量&#xff0c;变量的地址空间在线程栈空间 内&#xff0c;这种情况&#xff0c;变量归属单个线程&#xff0c;其他线程无法获得这种变量。 • 但有时候&#xff0c;很多变量都需要在线程间共享&#xff0c;这…...

奇异递归Template有啥奇的?

如果一个模版看起来很头痛&#xff0c;那么大概率这种模版是用来炫技&#xff0c;没啥用的&#xff0c;但是CRTP这个模版&#xff0c;虽然看起来头大&#xff0c;但是却经常被端上桌~ 奇异递归模板模式&#xff08;Curiously Recurring Template Pattern, CRTP&#xff09;是一…...

每天五分钟深度学习框架pytorch:神经网络工具箱nn的介绍

本文重点 我们前面一章学习了自动求导,这很有用,但是在实际使用中我们基本不会使用,因为这个技术过于底层,我们接下来将学习pytorch中的nn模块,它是构建于autograd之上的神经网络模块,也就是说我们使用pytorch封装好的神经网络层,它自动会具有求导的功能,也就是说这部…...

【办公软件】安全风险 Microsoft 已阻止宏运行,因为此文件的来源不受信任

Excel 2019版本&#xff0c;就出现安全风险 Microsoft 已阻止宏运行 因为此文件的来源不受信任的问题&#xff0c;宏直接就用不了了。 网上的解决方法&#xff0c;文件右键属性->取消安全锁。但存在没有安全锁这个选项。后查询到一个简单的解决方法。 打开Excel表格->文件…...

JavaScript语法基础之流程结构(顺序、选择、循环结构)

目录 1. 流程控制 1.1. 流程控制简介 1.1.1. 顺序结构 1.1.2. 选择结构 1.1.3. 循环结构 1.2. 选择结构&#xff1a;if 1.2.1. 单向选择&#xff1a;if… 1.2.2. 双向选择&#xff1a;if…else… 1.2.3. 多向选择&#xff1a;if…else_if…else… 1.3. 选择结构&#…...

集团数字化转型方案(四)

集团数字化转型方案通过全面部署人工智能&#xff08;AI&#xff09;、大数据分析、云计算和物联网&#xff08;IoT&#xff09;技术&#xff0c;创建了一个智能化的企业运营平台&#xff0c;涵盖从业务流程自动化、实时数据监控、精准决策支持&#xff0c;到个性化客户服务和高…...

【MySQL索引】索引失效场景

索引失效 1 全值匹配肯定不失效 2 最佳左前缀法则 索引文件具有 B-Tree 的最左前缀匹配特性&#xff0c;如果左边的值未确定&#xff0c;那么无法使用此索引。 3 主键插入顺序 页分裂&#xff0c;建议 让主键具有 AUTO_INCREMENT 4 计算、函数、类型转换(自动或手动)导致…...

基于MATLAB视觉的静态手势识别系统

一、课题介绍及思路 为了丰富手势识别方法的多样性&#xff0c;提高手势识别的正确率&#xff0c;提出了一种基于手势轮廓像素变化的手势识别方法。在Matlab环境下&#xff0c;设计并开发了一个基于视觉的静态手势识别系统。系统主要由两部分组成&#xff1a;手势分割与手势识…...

day02-作业题

一、简答题 请说出方法定义的全格式 访问修饰符 静态修饰符 返回值 方法名(参数列表){方法体;retrun 返回值;}请说出方法重载的概念 在一个类中&#xff0c;可以定义方法名相同&#xff0c;参数列表不相同(参数类型、参数个数或者参数顺序不同)的方法请简述什么是类&#xf…...

torch.cuda.set_divice()

我申请了两块GPU,然后看两张显卡的编号 import torch torch.cuda.set_device(0) # 设置当前cuda设备编号为1 print("当前cuda设备是", torch.cuda.current_device()) # 获取当前cuda设备import torch torch.cuda.set_device(1) …...

<数据集>RSOD数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;936张 标注数量(xml文件个数)&#xff1a;936 标注数量(txt文件个数)&#xff1a;936 标注类别数&#xff1a;4 标注类别名称&#xff1a;[aircraft, oiltank, overpass, playground] 序号类别名称图片数框数1air…...

企业高性能web服务器之Nginx

文章目录 Apache经典的web服务端Apache prefork 模型Apache work 模型&#xff08;适应市场&#xff09;Apache event 模型 网络I/O网络I/O模型I/O模型网络I/O模型 Nginx架构和安装Nginx源码编译环境准备安装nginx Nginx的平滑升级及版本回滚 Nginx架构和进程Nginx进程结构Ngin…...

11-sentinel利用nacos作持久化

本文介绍sentinel配置数据的持久化方法。由于sentinel官方并没有提供持久化功能&#xff0c;大家在测试过程中也能发现sentinel服务重启后&#xff0c;原来配置的数据就丢了&#xff0c;本文就是来处理这一问题的。 做好心理准备&#xff0c;我们要修改sentinel的源代码&#…...

密码学之哈希算法

文章目录 1. 哈希函数概述1.1 哈希函数的定义1.2 哈希函数的重要性 2. SHA系列算法简介2.1 SHA系列的发展历史2.2 SHA系列的应用场景 3. 主要SHA算法详解3.1 MD5算法3.2 SHA-1算法3.3 SHA-2算法家族3.4 SHA-3算法 4. SHA算法的安全性分析4.1 安全性的重要性4.2 已知的攻击方法4…...

杰发科技AC7801——GPIO通过寄存器地址控制高低电平

通过这个寄存器来查看控制的是哪个ODR值&#xff0c;使用sample&#xff0c;发现是0x20080068的第7和第9位 使用51控制寄存器的代码来置高置低代码&#xff0c;注意变量需要用unsigned int来声明 unsigned int ledBit 0;mdelay(100);ledBit | (1 << 9); ledBit & ~…...

代码随想录算法训练营第三十一天| 01背包问题 二维 01背包问题 一维 416. 分割等和子集

01背包问题 二维 代码随想录 视频讲解&#xff1a;带你学透0-1背包问题&#xff01;| 关于背包问题&#xff0c;你不清楚的地方&#xff0c;这里都讲了&#xff01;| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std;…...

github删除历史所有commit

背景 注意非确认情况下最好不要此操作 由于不小心在某些commits中提交了敏感信息&#xff0c;需要删除这些commits记录 网上看了很多方法&#xff0c;都是根据commit 找到一条id然后全部清除得&#xff0c;因为我是需要全部删除&#xff0c;所以有一种更简单得思路。 过程 1…...

C++前向声明简介

前向声明 class a; class b; class c:public d { ..... }类a和b已经实现了具体功能&#xff0c;类c在定义&#xff0c;在类c上面声明类a和b有什么作用 在类 c 的定义上面声明类 a 和 b 的作用主要是为了确保在编译时能够识别这两个类的存在&#xff0c;特别是在类 c 中可能会使…...

华为手机是越贵越好吗?

华为手机的价格与其性能、功能、设计以及市场定位等多种因素有关&#xff0c;因此不能简单地说华为手机越贵就越好。 首先&#xff0c;华为手机的产品线非常广泛&#xff0c;涵盖了从入门级到旗舰级的多个系列&#xff0c;每个系列都有其特定的目标用户群和市场需求。因此&…...

【java基础】IDEA 的断点调试(Debug)

目录 1.为什么需要 Debug 2.Debug的步骤 2.1添加断点 2.2单步调试工具介绍 2.2.1 Step Over 2.2.2 Step Into 2.2.3 Force Step Into 2.2.4 Step Out 2.2.5 Run To Cursor 2.2.6 Show Execution Poiint 2.2.7 Resume Program 3.多种 Debug 情况介绍 3.1行断点 3.2方…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...