[Linux#42][线程] 锁的接口 | 原理 | 封装与运用 | 线程安全
互斥量 mutex
• 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间 内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
• 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通 过数据的共享,完成线程之间的交互。
• 多个线程并发的操作共享变量,会带来一些问题。
例如想进 单人自习室,要拿钥匙,申请的是同一把锁
1. 锁的接口
定义锁
锁也是一个数据类型,它的类型是pthread_mutex_t
初始化
- 静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER(不需要销毁)
- 动态分配
- 函数原型:
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模拟}
批注:
LockGuard lockguard(&lock); // 临时的LockGuard对象, RAII风格的锁!
while 之后会自动解锁,利用了对象的生命周期
- 第二个{ ,明确出了锁的临界区
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 • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间 内,这种情况,变量归属单个线程,其他线程无法获得这种变量。 • 但有时候,很多变量都需要在线程间共享,这…...
奇异递归Template有啥奇的?
如果一个模版看起来很头痛,那么大概率这种模版是用来炫技,没啥用的,但是CRTP这个模版,虽然看起来头大,但是却经常被端上桌~ 奇异递归模板模式(Curiously Recurring Template Pattern, CRTP)是一…...
每天五分钟深度学习框架pytorch:神经网络工具箱nn的介绍
本文重点 我们前面一章学习了自动求导,这很有用,但是在实际使用中我们基本不会使用,因为这个技术过于底层,我们接下来将学习pytorch中的nn模块,它是构建于autograd之上的神经网络模块,也就是说我们使用pytorch封装好的神经网络层,它自动会具有求导的功能,也就是说这部…...
【办公软件】安全风险 Microsoft 已阻止宏运行,因为此文件的来源不受信任
Excel 2019版本,就出现安全风险 Microsoft 已阻止宏运行 因为此文件的来源不受信任的问题,宏直接就用不了了。 网上的解决方法,文件右键属性->取消安全锁。但存在没有安全锁这个选项。后查询到一个简单的解决方法。 打开Excel表格->文件…...
JavaScript语法基础之流程结构(顺序、选择、循环结构)
目录 1. 流程控制 1.1. 流程控制简介 1.1.1. 顺序结构 1.1.2. 选择结构 1.1.3. 循环结构 1.2. 选择结构:if 1.2.1. 单向选择:if… 1.2.2. 双向选择:if…else… 1.2.3. 多向选择:if…else_if…else… 1.3. 选择结构&#…...
集团数字化转型方案(四)
集团数字化转型方案通过全面部署人工智能(AI)、大数据分析、云计算和物联网(IoT)技术,创建了一个智能化的企业运营平台,涵盖从业务流程自动化、实时数据监控、精准决策支持,到个性化客户服务和高…...
【MySQL索引】索引失效场景
索引失效 1 全值匹配肯定不失效 2 最佳左前缀法则 索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。 3 主键插入顺序 页分裂,建议 让主键具有 AUTO_INCREMENT 4 计算、函数、类型转换(自动或手动)导致…...
基于MATLAB视觉的静态手势识别系统
一、课题介绍及思路 为了丰富手势识别方法的多样性,提高手势识别的正确率,提出了一种基于手势轮廓像素变化的手势识别方法。在Matlab环境下,设计并开发了一个基于视觉的静态手势识别系统。系统主要由两部分组成:手势分割与手势识…...
day02-作业题
一、简答题 请说出方法定义的全格式 访问修饰符 静态修饰符 返回值 方法名(参数列表){方法体;retrun 返回值;}请说出方法重载的概念 在一个类中,可以定义方法名相同,参数列表不相同(参数类型、参数个数或者参数顺序不同)的方法请简述什么是类…...
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数据集<目标检测>
数据集格式:VOCYOLO格式 图片数量:936张 标注数量(xml文件个数):936 标注数量(txt文件个数):936 标注类别数:4 标注类别名称:[aircraft, oiltank, overpass, playground] 序号类别名称图片数框数1air…...
企业高性能web服务器之Nginx
文章目录 Apache经典的web服务端Apache prefork 模型Apache work 模型(适应市场)Apache event 模型 网络I/O网络I/O模型I/O模型网络I/O模型 Nginx架构和安装Nginx源码编译环境准备安装nginx Nginx的平滑升级及版本回滚 Nginx架构和进程Nginx进程结构Ngin…...
11-sentinel利用nacos作持久化
本文介绍sentinel配置数据的持久化方法。由于sentinel官方并没有提供持久化功能,大家在测试过程中也能发现sentinel服务重启后,原来配置的数据就丢了,本文就是来处理这一问题的。 做好心理准备,我们要修改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值,使用sample,发现是0x20080068的第7和第9位 使用51控制寄存器的代码来置高置低代码,注意变量需要用unsigned int来声明 unsigned int ledBit 0;mdelay(100);ledBit | (1 << 9); ledBit & ~…...
代码随想录算法训练营第三十一天| 01背包问题 二维 01背包问题 一维 416. 分割等和子集
01背包问题 二维 代码随想录 视频讲解:带你学透0-1背包问题!| 关于背包问题,你不清楚的地方,这里都讲了!| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std;…...
github删除历史所有commit
背景 注意非确认情况下最好不要此操作 由于不小心在某些commits中提交了敏感信息,需要删除这些commits记录 网上看了很多方法,都是根据commit 找到一条id然后全部清除得,因为我是需要全部删除,所以有一种更简单得思路。 过程 1…...
C++前向声明简介
前向声明 class a; class b; class c:public d { ..... }类a和b已经实现了具体功能,类c在定义,在类c上面声明类a和b有什么作用 在类 c 的定义上面声明类 a 和 b 的作用主要是为了确保在编译时能够识别这两个类的存在,特别是在类 c 中可能会使…...
华为手机是越贵越好吗?
华为手机的价格与其性能、功能、设计以及市场定位等多种因素有关,因此不能简单地说华为手机越贵就越好。 首先,华为手机的产品线非常广泛,涵盖了从入门级到旗舰级的多个系列,每个系列都有其特定的目标用户群和市场需求。因此&…...
【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方…...
3个实战场景×5个核心技巧:Umi-OCR本地化部署与效率提升完全指南
3个实战场景5个核心技巧:Umi-OCR本地化部署与效率提升完全指南 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。内置…...
YOLOv5+Swin-Tiny实战:在自定义数据集上提升小目标检测精度的完整流程
YOLOv5与Swin-Tiny融合实战:工业级小目标检测优化指南 在无人机巡检、遥感监测和工业质检等场景中,小目标检测一直是计算机视觉领域的棘手挑战。传统卷积神经网络(CNN)在处理这类任务时,往往难以兼顾感受野与计算效率的平衡。本文将带您探索如…...
FRCRN处理长音频文件实战:切片、批处理与结果合并
FRCRN处理长音频文件实战:切片、批处理与结果合并 你是不是遇到过这样的问题?手头有一段长达数小时的会议录音、访谈素材或者播客音频,背景噪音让人头疼,想用FRCRN这样的降噪模型处理一下,结果发现模型一次只能处理几…...
地理计算引擎GeographicLib全解析:从场景应用到算法原理
地理计算引擎GeographicLib全解析:从场景应用到算法原理 【免费下载链接】geographiclib Main repository for GeographicLib 项目地址: https://gitcode.com/gh_mirrors/ge/geographiclib 一、核心场景应用解析:解决真实世界地理难题 在航海导航…...
TouchGal:3个关键功能让你成为真正的Galgame收藏家
TouchGal:3个关键功能让你成为真正的Galgame收藏家 【免费下载链接】kun-touchgal-next TouchGAL是立足于分享快乐的一站式Galgame文化社区, 为Gal爱好者提供一片净土! 项目地址: https://gitcode.com/gh_mirrors/ku/kun-touchgal-next 你是否曾为寻找心仪的…...
使用Alpine配置WSL ssh门户
1. 哑铃图是什么? 哑铃图(Dumbbell Plot),有时也称为DNA图或杠铃图,是一种用于比较两个相关数据点的可视化图表。 它源于人们对更有效数据比较方式的持续探索。 在传统的时间序列比较中,我们通常使用两条折…...
食品批发厂家口碑推荐榜
在食品批发行业,选择一家口碑良好的厂家至关重要。优质的食品批发厂家不仅能提供高品质的产品,还能保障稳定的供应和良好的售后服务。今天,我们就来为大家推荐一些口碑出众的食品批发厂家,其中惠州市佳德旺食品有限公司表现尤为突…...
PingFangSC字体工程化:从跨平台渲染挑战到企业级解决方案
PingFangSC字体工程化:从跨平台渲染挑战到企业级解决方案 【免费下载链接】PingFangSC PingFangSC字体包文件、苹果平方字体文件,包含ttf和woff2格式 项目地址: https://gitcode.com/gh_mirrors/pi/PingFangSC 一、问题诊断:揭开字体渲…...
4个关键阶段:让老旧Mac通过OpenCore Legacy Patcher实现系统兼容性与硬件加速解锁
4个关键阶段:让老旧Mac通过OpenCore Legacy Patcher实现系统兼容性与硬件加速解锁 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 老旧设备升级面…...
教育资源解析工具:打通国家中小学智慧教育平台电子课本获取通道
教育资源解析工具:打通国家中小学智慧教育平台电子课本获取通道 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具,帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载,让您更方便地获取课本内容。 …...
