[Linux]线程的互斥与同步
目录
一、互斥
1.互斥的概念
2.互斥锁接口
3.线程加锁解锁本质
4.死锁
二、同步
1.同步的概念
2.条件变量
3.条件变量接口
一、互斥
1.互斥的概念
互斥指的是任何时刻,互斥保证有且只有一个执行流进入临界区,进行临界资源的访问,通常对于临界资源起到保护作用的一种机制。
在多线程下,访问全局变量的话,一个线程的修改会影响到其他线程,如果说一些操作不是原子的话,会有数据不一致的问题。例如,多线程下抢电影票的情况,本来有count:100张票,但是最后可能卖出去100多张,是因为count--操作不是原子的。

当执行count--操作的时候,转化为汇编操作有3步,第一步是将count的值读取出来放入CPU寄存器中,第二步执行--操作,第三步将count的值覆盖会物理内存的位置,改变count在物理内存中的值。但是如果说在第二步执行完之后,该线程的时间片到了,就会从CPU上剥离下来,本来取出来的是10,在CPU中已经修改为9了,但是没有写回到内存中,所以内存的count值还是10,那么别的线程就会读取到count值是10,之后在CPU中在进行操作,并写回物理内存的值也为9。等到第一个修改为9的进程再次回到CPU中执行时,会执行第三步操作,将9有一次的写回到了物理内存当中。此时count的值是多少都不知道,可能已经减为0了,但是又被写回了9,所以说不是原子的操作在多线程下会有很大的问题。
又或者是多线程下一个变量充当if判断条件的时候,可能刚读取到该变量,时间片就到了,没有来得及进行判断,就被剥离了下来,其他的线程继续操作,最终把变量改为了不满足if判断条件了,本来该线程应该判断后退出的,但是该线程已经取出了该条件变量,是最开始的值,所以还是会判断符合条件执行if内部代码,所以说也会出错的。
数据在内存是被多线程共享的,但是一旦读取到寄存器当中,就变成了线程的上下文数据,就属于私有了。所以互斥就是为了解决上述的数据不一致问题,就是同一时间只让一个线程进行访问该资源。
2.互斥锁接口
上述的问题可以使用互斥锁来解决,锁是用来保护临界区在同一时间只能由一个进程进行访问的。锁相当于一个资源,多线程去竞争该资源,但是只有一个线程可以获取到该资源,其他线程就会阻塞在锁的位置等待锁资源,获取到锁资源的线程才可以去访问临界区的资源。当线程执行完临界区的操作之后,释放锁,其他线程才可以继续竞争锁资源。
全局锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
创建局部锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
第一个参数是要初始化的锁对象,第二个参数是设置锁的属性,一般设置为nullptr即可。
销毁局部锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
对于pthread_mutex_lock加锁是阻塞式的申请锁资源,如果没有申请到的话,则会阻塞等待锁资源,而pthread_mutex_trylock则是尝试申请锁资源,也就是非阻塞式的申请,如果有的话就申请到了,没有的话就不申请了,直接返回。
3.线程加锁解锁本质
多线程去申请锁资源,锁保证了线程之间的互斥,但是锁也是共享资源啊,谁来保证锁的安全性呢?所以要和信号量一样,对于申请锁的操作设置为原子的。该原子的操作是基于硬件指令的支持,比如说比较并交换的CAS等指令就是一步执行的操作操作。把申请锁的操作转换为原子的汇编指令就保证了申请锁的原子性。
pthread_mutex_lock()接口的加锁实现:
lock:
movb %0, %al
xchgb %al, mutex
if(al寄存器 > 0) return 0;
else 挂起等待
go to lock;
首先将al寄存器的内容置为0,之后xchgb交换al寄存器和mutex标记位中的值,当有锁资源的时候mutex的标记位是1,没有的时候是0。xchagb就是一种原子性的操作指令,也就是swap操作。 交换之后判断是否申请到了锁资源,也就是判断al寄存器交换过来的数据是不是1,如果是1的话证明获取了锁资源就可以执行访问临界区的代码了,如果没有获取到,那么就会进入else阻塞的等待,如果有锁资源了,会跳转到lock开头重复上述动作进行申请锁资源。

当有一批线程申请该锁资源的时候,第一个进行swap交换的线程会将mutex的标记位交换位0,而1则是进入了该线程的al寄存器中,判断al寄存器大于0跳出循环,执行临界区代码。此时锁的标记位就是0了,其他线程来了之后,再怎么交换al寄存器中的值都是0,所以都需要进行阻塞等待。
当线程调度切换的时候,也不会有影响,其他线程来的时候还是要阻塞等待,因为标志位1被申请到锁资源的线程通过切换上下文数据而带走了,只有等到该线程重新被CPU调度,执行完临界区代码,将标志位1交换会mutex,别的线程才能交换到标志位为1,才能让al寄存器大于0,从而获得到锁资源,返回执行临界区代码。
也就是说锁资源的标志位,就相当于一把钥匙,也只要一把钥匙,那么一个人拿走该钥匙之后,不管什么情况,当这个人没用完之前,没把钥匙换回来之前,其他人都打不开这个门,都需要在门口进行等待。
pthread_mutex_unlock()接口的解锁实现:
unlock:
movb $1, mutex
唤醒阻塞等待的进程
return 0;
解锁的操作,就是将标志位的1交换会锁资源中,然后唤醒等待的线程去竞争锁资源。
4.死锁
死锁指的是一组线程中,各个线程均占有不会被释放掉的资源,但又因相互申请被其他线程锁占用不会释放的资源而处于一种持久等待的状态,称之为死锁。例如下面的图所示,有两个资源A和B,1号线程占有A资源,2号线程占有B资源,而两者又互相想要对方的资源,所以都在互相等待。

一个线程也可以产生死锁,可以让一个进程对同一个锁资源重复申请两次就变成了拥有了该资源还在申请该资源的情况,就变为了死锁。
那么死锁如何避免呢?首先死锁的产生拥有四个条件:互斥条件、请求与保持条件、不剥夺条件、循环等待条件。请求与保持指的是一个执行流因请求资源而阻塞的时候,对以获得的资源保持不放,并且还在一直申请资源。循环等待指的是若干个执行流之间形成了一种头尾详解的循环等待资源的关系。
解决死锁的方式就是破坏其中一个条件即可。例如:不适用锁;在申请锁资源不成功的时候,把自己拥有的资源释放掉;强行将别人的资源抢占过来;按照同样的顺序申请锁资源,就是对于多个资源的申请做一个先后顺序,先有A资源,才可以申请B资源,而不是所有资源都可以无条件的申请。
二、同步
1.同步的概念
同步指的是在临界资源使用安全的情况下,让多线程的执行具有一定的顺序性,能够较为充分的利用资源。对于竞争锁资源谁竞争到,我们是无法控制的,但是我们可以通过一些方式来规定谁可以去竞争锁资源来实现线程访问临界资源具有一定的顺序性。
2.条件变量
条件变量是一种用于线程同步的机制。它主要用于让线程在某个条件满足之前等待,当条件满足时,线程可以被唤醒并继续执行。在多线程编程环境中,条件变量提供了一种高效的方式来协调线程之间的执行顺序和资源访问。通常与互斥锁配合使用。互斥锁用于保护共享资源的访问,而条件变量用于线程的等待和唤醒操作。
简单可以理解为他内部有一个标记位和一个线程等待队列,当一个线程申请到锁资源的时候,会访问临界区的代码,此时我们在临界区代码中判断条件变量是否就绪,如果说就绪的话,条件变量的标记位为1,就可以让申请到锁的进行继续访问临界区资源,如果说没有准备就绪的话,就把该线程链入到线程等待队列中,同时释放锁资源。当条件准备就绪之后,会唤醒等待队列的一个或多个进程继续竞争锁资源,竞争到锁资源的线程就可以访问临界区资源了。
下面举一个场景,有两个线程和一个缓冲区临界资源,一个线程为写入线程,一个线程为读取线程。那么显而易见,需要先写入后读取,那么这就是线程之间访问临界资源的顺序,也就是线程的同步策略。那么如何实现呢?用一把锁和一个条件变量去实现,该条件变量关联的条件就是缓冲区中有数据。当读取进程申请到锁的时候,会进入条件变量的判断,因为条件变量没有就绪,所以会就读写进程链入到线程等待队列中,释放锁资源。那么写入线程就会竞争到锁资源,进行写入,写入之后,会调用条件变量就绪接口并唤醒等待该条件变量的等待队列中的线程,之后释放锁资源。那么读取线程就可以继续参与锁的竞争了,当获取到锁资源后,就可以读取临界区中的资源数据了。这就是使用条件变量和锁实现的线程同步。
3.条件变量接口
全局条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
条件变量创建
int pthread_cond_init(pthread_cont_t* cond, const pthread_condattr_t* attr);
条件变量销毁
int pthread_cond_destroy(pthread_cond_t* cond);
等待条件变量
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
唤醒等待队列中的线程
int pthread_cond_signal(pthread_cond_t* cond); //唤醒队列头部的一个线程
int pthread_cond_broadcast(pthread_cond_t* cond); //唤醒所有线程
接口的使用代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>//全局锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//全局条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void* handler(void* arg)
{while(true){//申请锁资源pthread_mutex_lock(&mutex);//等待条件变量就绪pthread_cond_wait(&cond, &mutex);//临界区std::cout << pthread_self() << "-thread entry critical section" << std::endl;//释放锁资源pthread_mutex_unlock(&mutex);}
}int main()
{//创建线程for(int i = 0; i < 5; i++){pthread_t tid;pthread_create(&tid, nullptr, handler, nullptr);//线程分离--无需等待pthread_detach(tid);}//反复让条件变量就绪,唤醒一个线程while(true){pthread_cond_signal(&cond);sleep(1);}return 0;
}
对于在条件变量等待队列被唤醒的线程,也要重新参与锁的竞争,但是并非是跳到申请锁的那行代码,而是说该pthreat_cont_wait函数内部也是有申请锁的代码的。
相关文章:
[Linux]线程的互斥与同步
目录 一、互斥 1.互斥的概念 2.互斥锁接口 3.线程加锁解锁本质 4.死锁 二、同步 1.同步的概念 2.条件变量 3.条件变量接口 一、互斥 1.互斥的概念 互斥指的是任何时刻,互斥保证有且只有一个执行流进入临界区,进行临界资源的访问,通…...
Java:缓存:LinkedHashMap实现Lru
文章目录 Lru源码分析 LinkedHashMap维护一个LinkedHashMapEntry<K,V>的双向链表对LinkedHashMap的增删查操作,也会对链表进行相同的操作并改变链表的链接顺序小结使用方法应用总结Lru Least Recently Used,…...
【形式篇】年终总结怎么写:PPT如何将内容更好地表现出来
——细节满满,看完立马写出一篇合格的PPT 总述 形式服务于内容,同时合理的形式可以更好地表达和彰显内容 年终总结作为汇报型PPT,内容一定是第一位的,在内容篇(可点击查看)已经很详细地给出了提纲思路,那如何落实到…...
自定义字典转换器用于easyExcel 导入导出
文章目录 引言I 字典转换器、注解、序列化器注解定义自定义字典转换器用于easyExcel 导入导出自定义字典序列化器II 字典存储设计数据库表结构redis缓存引言 需求导入Excel时,根据字典内容或者字段编码转换 导出Excel时,根据字典内容或者字段编码转换 接口响应数据序列化时,…...
0 Token 间间隔 100% GPU 利用率,百度百舸 AIAK 大模型推理引擎极限优化 TPS
1. 什么是大模型推理引擎 大模型推理引擎是生成式语言模型运转的发动机,是接受客户输入 prompt 和生成返回 response 的枢纽,也是拉起异构硬件,将物理电能转换为人类知识的变形金刚。 大模型推理引擎的基本工作模式可以概括为,…...
js:事件流
事件流 事件流是指事件完整执行过程中的流动路径 一个事件流需要经过两个阶段:捕获阶段,冒泡阶段 捕获阶段是在dom树里获取目标元素的过程,从大到小 冒泡阶段是获取以后回到开始,从小到大,像冒泡一样 实际开发中大…...
Linux对比Windows
1. 性能和资源占用 Linux 更轻量级:Linux 内核设计简洁,占用系统资源(如内存、CPU)较少,适合高负载的服务器环境。 高效的多任务处理:Linux 在多任务处理和并发请求方面表现优异,适合处理大量并…...
Excel 技巧03 - 如何对齐小数位数? (★)如何去掉小数点?如何不四舍五入去掉小数点?
这几个有点儿关联,我都给放到一起了,不影响大家分别使用。 目录 1,如何对齐小数位数? 2,如何去掉小数点? 3,如何不四舍五入去掉小数点? 1,如何对齐小数位数ÿ…...
Vue3国际化多语言的切换
参考链接: link Vue3国际化多语言的切换 一、安装 vue-i18n 和 element-plus vue-i18n 是一个国际化插件,专为 Vue.js 应用程序设计,用于实现多语言支持。它允许你将应用程序的文本、格式和消息转换为用户的首选语言,从而提供本地化体验。…...
使用XAML语言仿写BiliBil登录界面
实现步骤 实现左右布局 使用了Grid两列的网格布局,第一列宽度占35%,第二列宽度占65%。使用容器布局Border包裹左右布局内容,设置背景色、设置圆角 <!-- 定义两列--> <Grid.ColumnDefinitions><ColumnDefinition Width &quo…...
机器学习和深度学习
机器学习(Machine Learning,简称 ML)和深度学习(Deep Learning,简称 DL)都是人工智能(AI)领域的重要技术,它们的目标是使计算机通过数据学习和自主改进,从而完…...
Word表格批量提取数据到Excel,Word导出到Excel,我爱excel
Word表格批量提取数据到Excel,Word导出到Excel - 我爱Excel助你高效办公 在日常办公中,Word表格常常用于记录和整理数据,但将这些数据从Word提取到Excel,特别是当涉及多个文件时,常常让人头疼。如果你经常需要将多个W…...
SpringSecurity抛出异常但AccessDeniedHandler不生效
文章目录 复现原因 复现 Beanpublic SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {//...//异常http.exceptionHandling(except -> {except.authenticationEntryPoint(new SecurityAuthenticationEntryPoint());except.accessDeniedHandle…...
高清绘画素材3600多张动漫线稿线描上色练习参考插画原画
工作之余来欣赏一波线稿,不务正业版... 很多很多的线稿... 百度网盘 请输入提取码...
EXCEL技巧
1. EXCEL技巧 1.1. 截取表格内某个字符之前的所有字符 1.1.1.样例 在单元格内输入函数: # 截取A1单元格内“分”字符左边的所有字符 LEFT(A1,FIND("分",A1)-1)1.1.2.截图...
python制作翻译软件
本文复刻此教程:制作属于自己的翻译软件-很简单【Python】_哔哩哔哩_bilibili 一、明确需求(以搜狗翻译为例) (1)网址:https://fanyi.sogou.com/text (2) 数据:翻译内容…...
ollama+FastAPI部署后端大模型调用接口
ollamaFastAPI部署后端大模型调用接口 记录一下开源大模型的后端调用接口过程 一、ollama下载及运行 1. ollama安装 ollama是一个本地部署开源大模型的软件,可以运行llama、gemma、qwen等国内外开源大模型,也可以部署自己训练的大模型 ollama国内地…...
BERT:深度双向Transformer的预训练用于语言理解
摘要 我们介绍了一种新的语言表示模型,名为BERT,全称为来自Transformer的双向编码器表示。与最近的语言表示模型(Peters等,2018a;Radford等,2018)不同,BERT旨在通过在所有层中联合调…...
【AI-23】深度学习框架中的神经网络3
神经网络有多种不同的类型,每种类型都针对特定的任务和数据类型进行优化。根据任务的特点和所需的计算能力,可以选择适合的神经网络类型。以下是一些主要的神经网络类型及其适用的任务领域。 1. 深度神经网络(DNN) 结构…...
网站运营数据pv、uv、ip
想要彻底弄清楚pv uv ip的区别,首先要知道三者的定义: IP(独立IP)的定义: 即Internet Protocol,指独立IP数。24小时内相同公网IP地址只被计算一次。 PV(访问量)的定义: 即Page View,即页面浏览量或点击量,用户每次刷…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...
