【Linux-多线程】线程互斥(锁和它的接口等)
一、线程互斥
我们把多个线程能够看到的资源叫做共享资源,我们对共享资源进行保护,就是互斥
1.多线程访问问题
【示例】见一见多线程访问问题,下面是一个抢票的代码,共计票数10000张,4个线程去抢
之前我们展示过封装代码,这里我们直接使用
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "mythread.hpp"using namespace TreadMoudle;int tickets = 10000;void route(const std::string &name)
{while (true){if (tickets > 0){// 抢票过程usleep(1000); // 1ms -> 抢票花费的时间printf("who: %s, get a ticket: %d\n", name.c_str(), tickets);tickets--;}else{break;}}
}int main()
{Thread t1("thread-1", route);Thread t2("thread-2", route);Thread t3("thread-3", route);Thread t4("thread-4", route);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();
}
【测试结果】:四个线程均抢完票,并且等待成功,回收成功;但是我们却发现一个问题,票一共只有1w张,理应最后一个线程得到的票号是1,这里确出现了负数,这是什么原因?
【解释】:计算机的运算类型有算数运算和逻辑运算,并且是CPU中寄存器进行运算,在CPU内,寄存器只有一套,但是寄存器里面的数据,可以有多套;这些数据属于线程私有,看起来放在了一套公共的寄存器中,但是属于线程私有,当他被切换的时候,他要带走自己的数据!回来的时候会恢复;
-
我们平常所用的一条代码,在汇编层上可能会对应很多汇编语句,比如一个简单的 tickets-- ,就牵涉到至少三条指令:1.重读数据,2.--数据,3.写回数据;
-
因此在进入抢票的过程中,看似就几行代码,到了汇编层就是很多代码,CPU是会进行线程切换,这样就会发生数据不一至的问题,如何理解?我们看图
如何解决这种问题?加锁!!
2.认识锁和它的接口
pthread_mutex_lock
pthread_mutex_lock
是一个在多线程编程中用于锁定互斥量(mutex)的函数。以下是关于 pthread_mutex_lock
的详细说明:
函数原型:该函数的原型定义如下:
参数:pthread_mutex_t是互斥锁的类型,任何时刻,只允许一个线程进行资源访问
功能描述:当调用 pthread_mutex_lock
时,它将尝试锁定 mutex
参数指向的互斥量。如果这个互斥量当前没有被锁定,它将被锁定,并且调用该函数的线程将成为互斥量的所有者,函数会立即返回。如果互斥量已经被其他线程锁定,那么调用该函数的线程将会阻塞,直到互斥量被解锁。
互斥量的状态:互斥量有两种状态:未锁定(此时不被任何线程拥有)和锁定(此时被一个线程拥有)。一个互斥量不能同时被两个不同的线程所拥有。如果一个线程尝试锁定一个已经被其他线程锁定的互斥量,它将会等待,直到那个线程解锁互斥量。
返回值:如果函数执行成功,返回值为 0。如果发生错误,例如尝试重新锁定已经被同一个线程锁定的互斥量,函数将返回一个错误码。
pthread_mutex_t
pthread_mutex_t
是 POSIX 线程(通常称为 pthreads)API 中定义的一个数据类型,用于表示互斥量(mutex)。互斥量是一种同步机制,用于防止多个线程同时访问共享资源,从而避免竞态条件。
定义:
注意,pthread_mutex_t
是一个不透明的数据类型,其内部结构对用户是隐藏的。用户不应该尝试直接访问或修改这个结构体的内容。
初始化: 在使用 pthread_mutex_t
之前,必须对其进行初始化。互斥量可以通过以下几种方式进行初始化:
静态初始化(锁是全局的或者静态的):可以在声明时直接使用宏 PTHREAD_MUTEX_INITIALIZER
进行初始化。
动态初始化:使用 pthread_mutex_init
函数进行初始化。
其中 attr
是一个指向 pthread_mutexattr_t
结构的指针,该结构用于设置互斥量的属性。如果 attr
为 NULL
,则互斥量将使用默认属性。
销毁: 当不再需要互斥量时,应该使用 pthread_mutex_destroy
函数来释放它所占用的资源。
在销毁一个互斥量之前,必须确保没有线程正在等待或持有该互斥量。
锁定与解锁:
-
使用
pthread_mutex_lock
尝试锁定互斥量。如果互斥量已被锁定,调用线程将阻塞直到互斥量被解锁。 -
使用
pthread_mutex_trylock
尝试锁定互斥量,但不会阻塞;如果互斥量已被锁定,则立即返回一个错误码。 -
使用
pthread_mutex_unlock
解锁互斥量。
属性: 互斥量可以有不同的属性,如类型(普通、递归、错误检查等),这些属性可以通过 pthread_mutexattr_t
结构来设置。
错误处理: 所有与互斥量相关的函数在出错时都会返回错误码,可以通过 strerror
函数或 perror
函数来获取错误信息。
pthread_mutex_lock / _unlock
pthread_mutex_lock
是 POSIX 线程(pthreads)库中的一个函数,用于锁定一个互斥量(mutex)。当一个线程调用 pthread_mutex_lock
尝试锁定一个互斥量时,以下情况可能会发生:
❍ 如果互斥量当前是未锁定的状态,调用线程会成功锁定该互斥量,并继续执行。
❍ 如果互斥量已经被另一个线程锁定,调用线程将会阻塞,直到该互斥量被解锁。
pthread_mutex_unlock
函数,这是一个 POSIX 线程(pthreads)库中的函数,用于解锁一个互斥量(mutex)。当一个线程完成了对临界区的访问后,它应该解锁互斥量,以便其他线程可以锁定并访问该临界区。
pthread_mutex_trylock
pthread_mutex_trylock
是 POSIX 线程(pthreads)库中的一个函数,用于尝试锁定一个互斥量(mutex),但它与 pthread_mutex_lock
的主要区别在于,如果互斥量已经被锁定,pthread_mutex_trylock
不会阻塞调用线程,而是立即返回一个错误码。
返回值:
-
成功时(互斥量被成功锁定),返回 0。
-
如果互斥量已经被锁定,返回
EBUSY
。 -
出现其他错误时,返回其他错误编号。
使用场景:
-
非阻塞互斥量锁定:当线程不希望因等待互斥量而阻塞时,可以使用
pthread_mutex_trylock
。 -
避免死锁:通过尝试锁定互斥量,线程可以决定是否继续执行或采取其他操作,从而避免死锁。
-
优先级继承:在某些实时系统中,为了避免优先级反转,可以使用
pthread_mutex_trylock
来尝试锁定互斥量。
注意事项
-
错误处理:调用
pthread_mutex_trylock
时,应检查返回值,并根据返回值做出相应的处理。 -
资源释放:如果
pthread_mutex_trylock
返回EBUSY
,线程应该释放已经持有的资源,避免资源泄露。 -
重试策略:通常在使用
pthread_mutex_trylock
时,如果返回EBUSY
,线程可能会在一段时间后重试锁定。
学会锁的基本使用后我们就可以修改我们自己实现的多线程,并且重新进行抢票
mythread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>namespace ThreadMoudle
{class ThreadData{public:ThreadData(const std::string &name, pthread_mutex_t *lock):_name(name), _lock(lock){}public:std::string _name;pthread_mutex_t *_lock;};// 线程要执行的方法,后面我们随时调整typedef void (*func_t)(ThreadData *td); // 函数指针类型class Thread{public:void Excute(){std::cout << _name << " is running" << std::endl;_isrunning = true;_func(_td);_isrunning = false;}public:Thread(const std::string &name, func_t func, ThreadData *td):_name(name), _func(func), _td(td){std::cout << "create " << name << " done" << std::endl;}static void *ThreadRoutine(void *args) // 新线程都会执行该方法!{Thread *self = static_cast<Thread*>(args); // 获得了当前对象self->Excute();return nullptr;}bool Start(){int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);if(n != 0) return false;return true;}std::string Status(){if(_isrunning) return "running";else return "sleep";}void Stop(){if(_isrunning){::pthread_cancel(_tid);_isrunning = false;std::cout << _name << " Stop" << std::endl;}}void Join(){::pthread_join(_tid, nullptr);std::cout << _name << " Joined" << std::endl;delete _td;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _isrunning;func_t _func; // 线程要执行的回调函数ThreadData *_td;};
} // namespace ThreadModle
main.cc
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "mythread.hpp"using namespace ThreadMoudle;
int tickets = 10000; // 共享资源,造成了数据不一致的问题pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
static int threadnum = 4;void route(ThreadData *td)
{// std::cout <<td->_name << ": " << "mutex address: " << td->_lock << std::endl;// sleep(1);while (true){pthread_mutex_lock(td->_lock);if (tickets > 0){// 抢票过程usleep(1000); // 1ms -> 抢票花费的时间printf("who: %s, get a ticket: %d\n", td->_name.c_str(), tickets);tickets--;pthread_mutex_unlock(td->_lock);}else{pthread_mutex_unlock(td->_lock);break;}}
}int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex, nullptr);std::vector<Thread> threads;for (int i = 0; i < threadnum; i++){std::string name = "thread-" + std::to_string(i + 1);ThreadData *td = new ThreadData(name, &mutex);threads.emplace_back(name, route, td);}for (auto &thread : threads){thread.Start();}for (auto &thread : threads){thread.Join();}pthread_mutex_destroy(&mutex);return 0;
}
【结果】:此时重新执行程序就不会出现数据不一致问题了
所谓的对临界资源进行保护,本质是对临界区代码进行保护
我们对所有资源进行访问,本质都是通过代码进行访问的!保护资源,本质就是把访问资源的代码保护起来
3.解决历史问题
-
所以,加锁的范围,粒度一定要尽量小
-
任何线程,要进行抢票,都得先申请锁,原则上不应该有例外
-
所有线程申请锁,前提是所有的线程都得看到这把锁,锁本身也是共享资源----加锁的过程,必须是原子的
-
原子性:要么不做,要做就做完,没有中间状态,就是原子性
-
如果线程申请锁失败了,我的线程要被阻塞
-
如果线程申请锁成功了,继续向后运行
-
如果线程申请锁成功了,执行临界区的代码了,执行临界区代码期间是可以发生切换的(比如时间片到了),但是即使切换了,其他线程无法进入!因为我虽然被切换了,但是我没有释放锁,我可以放心的执行完毕,没有人能打扰我
结论:所以对于其他线程,要么我没有申请锁,要么我释放了锁,对其他线程才有意义
4.原理角度理解这个锁
5.从实现角度理解锁
✸ 经过上面的例子,大家已经意识到单纯的i++或者++i 都不是原子的,有可能会有数据一致性问题
✸ 为了实现互斥锁的操作,大多数体系结构都提供了 swap 或 exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台访问内存的,总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。
◉ CPU的寄存器只有一套,被所有的线程共享。但是寄存器里面的数据,属于执行流的上下文,属于执行流私有的数据
◉ CPU在执行代码的时候,一定要有对应的执行载体 -- 线程 && 进程
◉ 数据在内存中,被所有线程是共享的
结论:把数据从内存移动到CPU寄存器中,本质是把数据从共享,变成线程私有
相关文章:

【Linux-多线程】线程互斥(锁和它的接口等)
一、线程互斥 我们把多个线程能够看到的资源叫做共享资源,我们对共享资源进行保护,就是互斥 1.多线程访问问题 【示例】见一见多线程访问问题,下面是一个抢票的代码,共计票数10000张,4个线程去抢 之前我们展示过封…...

[江科大STM32] 第五集快速建立STM32工程模板——笔记
保存,进去选芯片型号,我们是F10C8T6 一个MD,还有所有.c.h 这里所有文件 这里所有文件...

流水线并行举例说明;GPU 的细粒度问题
GPU 的细粒度与模型并行和流水线并行关系 使用模型并行和流水线并行之后会涉及到一个模型切分细粒度的问题,先切分多头(并行执行),每一个多头在切分不同阶段(串行执行)。这种情况下GPU的细粒度是多少 在这种模型并行和流水线并行结合且按多头和阶段切分的情况下,GPU 的…...

如何确保Kafka集群的高可用?
大家好,我是锋哥。今天分享关于【如何确保Kafka集群的高可用?】面试题。希望对大家有帮助; 如何确保Kafka集群的高可用? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 要确保 Kafka 集群 的高可用性,需要…...

计算机毕业设计Python+Spark考研预测系统 考研推荐系统 考研数据分析 考研大数据 大数据毕业设计 大数据毕设
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

Oracle SqlPlus常用命令简介
参考资料 【SQL*Plus】SETシステム変数の設定前後の具体例 目录 一. 执行系命令1.1 执行系统命令1.2 执行sql脚本文件1.2.1 在数据库中执行sql脚本1.2.2 通过sqlplus执行sql脚本 二. show命令2.1 显示SqlPlus中的全部环境变量2.2 显示指定环境变量的设置 三. 时间显示3.1 set …...

8.若依系统监控与定时任务
帮助开发者和运维快速了解应用程序的性能状态。 数据监控 定时任务 实现动态管理任务。 需求:每间隔5s,控制台输出系统时间。 新建的任务类必须在指定目录ruoyi-quartz模块下的task包下。 状态设置为启动 执行策略 场景:比如一个任务每个…...

《计算机组成及汇编语言原理》阅读笔记:p160-p176
《计算机组成及汇编语言原理》学习第 12 天,p160-p176 总结,总计 17 页。 一、技术总结 1.PowerPC (1)programming model(mode) As in most modern computers, there are at least two separate views of the system (formally called programming m…...

TCP网络编程(三)—— 客户端的编写/服务器端和客户端的通信
上篇文章我们学习了TCP的服务器端模式的编写,这篇文章我们将开始编写客户端的代码,完成服务器端和客户端的通信。完整代码和演示在文章的后面。 和服务器端不同,在客户端我们只需要服务器端的套接字和服务器端的地址和端口,用于向…...

如何在谷歌浏览器中使用自定义模板
作为最常用的网络浏览器之一,谷歌浏览器不仅提供了强大的功能,还允许用户通过各种方式自定义其外观和功能。其中,使用自定义模板可以极大地提升用户体验,无论是更改浏览器的外观还是优化网页显示效果。本文将详细介绍如何在谷歌浏…...

Day2 微服务 网关路由转发、网关登录校验、配置管理
目录 1.网关路由转发 1.1 网关 1.2 快速入门 1.2.1 创建项目 1.2.2 引入依赖 1.2.3 启动类 1.2.4 配置路由 1.2.5 测试 1.3 路由过滤 2.网关登录校验 2.1 鉴权思路分析 2.2 网关过滤器 2.3 自定义过滤器 2.3.1 自定义GatewayFilter 2.3.2 自定义GlobalFilter 2.4 登录校验 2.4.…...

Android 旋转盘导航栏
1.直接上源码: package com.you.arc;import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.RectF; import android.support…...

空域降噪、频域降噪和时域降噪
目录 算法原理: 1.图像噪声 2.图像中常见的噪声的类型 3.不同域的定义 4.空域降噪 4.1.空域降噪的定义: 4.2.思想核心: 4.3.局部的线性算法 高斯降噪 4.4.非局部算法 5.频域降噪 傅里叶降噪: 小波降噪: …...

Cornerstone3D:了解Nifti文件,并查看元数据
Nifti 全称Neuroimaging Informatics Technology Initiative是一种专为存储医学和神经影像数据而设计的文件格式。设计目的是高效的存储三维或四维图像数据,同时将相关的元数据紧凑地嵌入文件中。Nifti文件的组成:头信息(元数据)…...

设计模式の状态策略责任链模式
文章目录 前言一、状态模式二、策略模式三、责任链模式 前言 本篇是关于设计模式中的状态模式、策略模式、以及责任链模式的学习笔记。 一、状态模式 状态模式是一种行为设计模式,核心思想在于,使某个对象在其内部状态改变时,改变该对象的行为…...

DevOps流程CICD之Jenkins使用操作
一、jenkins的docker-compose安装部署 请参考 jenkins的docker安装部署配置全网最详细教程-CSDN博客 二、创建repository 三、创建ssh 四、创建视图 五、创建任务 六、配置gitlab钩子 七、自动构建部署CI/CD验证...

【玩转23种Java设计模式】行为型模式篇:备忘录模式
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 汇总目录链接&…...

Unity Shader TexelSize的意义
TexelSize在制作玻璃折射效果时会用到。 // Get the normal in tangent space fixed3 bump UnpackNormal(tex2D(_BumpMap, i.uv.zw)); // Compute the offset in tangent space float2 offset bump.xy * _Distortion * _RefractionTex_TexelSize.xy; i.scrPos.xy offset * i…...

三、STM32MP257系列之定制Yocto Machine
文章目录 STM32MP257系列之定制的Yocto Machine1. TFA 定制2. OPTEE OS定制3. Uboot 定制3.1 创建 board3.2 创建 board的头文件3.3 创建 board的配置文件3.4 添加我们自己的dtb文件3.5 生成新patch打包到uboot recipe中3.6 修改yocto中的配置 4. Kernel 定制4.1 定制设备树 5.…...

小程序信息收集(小迪网络安全笔记~
免责声明:本文章仅用于交流学习,因文章内容而产生的任何违法&未授权行为,与文章作者无关!!! 附:完整笔记目录~ ps:本人小白,笔记均在个人理解基础上整理,…...

使用 Docker 搭建 Drogon 框架
使用 Docker 搭建 Drogon 框架 Drogon 是一个基于 C 的高性能 Web 框架,支持异步 I/O 和协程。使用 Docker 可以快速搭建 Drogon 开发环境,避免依赖冲突和配置问题。 以下是使用 Docker 搭建 Drogon 框架的详细步骤: 1. 准备工作 安装 Doc…...

【Linux报告】实训一:GNME桌面环境的设置及应用
实训一:GNME桌面环境的设置及应用 【练习1】在图形模式和文本模式下登录Linux系统。 1、开启Linux虚拟机。 答:打开此虚拟机如图所示 2、观察屏幕上显示的启动信息。 3、当系统启动到图形界面时,用普通用户身份登录。 答:如图…...

活动预告 |【Part1】Microsoft Azure 在线技术公开课:基础知识
课程介绍 参加“Azure 在线技术公开课:基础知识”活动,培养有助于创造新的技术可能性的技能并探索基础云概念。参加我们举办的本次免费培训活动,扩充自身的云模型和云服务类型知识。你还可以查看以计算、网络和存储为核心的 Azure 服务。 活…...

vulnhub靶场【Hogwarts】之bellatrix
前言 靶机:hotwarts-dobby,ip地址为192.168.1.69 攻击:kali,ip地址为192.168.1.16 都采用虚拟机,网卡为桥接模式 主机发现 使用arp-scan -l或netdiscover -r 192.168.1.1/24扫描发现主机 信息收集 使用nmap扫描端…...

移动 APP 设计规范参考
一、界面设计规范 布局原则: 内容优先:以内容为核心进行布局,突出用户需要的信息,简化页面导航,提升屏幕空间利用率.一致性:保持界面元素风格一致,包括颜色、字体、图标等,使用户在…...

HarmonyOS:@Require装饰器:校验构造传参
一、前言 Require是校验Prop、State、Provide、BuilderParam和普通变量(无状态装饰器修饰的变量)是否需要构造传参的一个装饰器。 说明 从API version 11开始对Prop/BuilderParam进行校验。 从API version 11开始,该装饰器支持在元服务中使用。 从API version 12开…...

github提交不上去,网络超时问题解决
问题出现的原因: DNS服务器数据不同步,github的服务器发送迁移,在本地缓存的ip地址现在无效了。 解决方案: 1)点击这里,查询github.com最新的ip地址 2.0)编辑linux系统地址缓存文件&#x…...

国产数据库OceanBase从入门到放弃教程
1. 介绍 是由蚂蚁集团(Ant Group,原蚂蚁金服)自主研发的分布式关系型数据库。它旨在解决海量数据存储和高并发访问的问题,特别适合金融级应用场景,如支付宝等对数据一致性、可靠性和性能有极高要求的服务。以下是关于…...

风力涡轮机缺陷检测数据集,91.4%准确识别率,18912张图片,支持yolo,PASICAL VOC XML,COCO JSON格式的标注
风力涡轮机缺陷检测数据集,91.4%准确识别率,18912张图片,支持yolo,PASICAL VOC XML,COCO JSON格式的标注 数据集下载: yolo v&#…...

Rabbitmq追问2
分析rabbitmq 默认使用姿势是什么 direct fanout还是什么 public void convertAndSend(String exchange, String routingKey, Object object, CorrelationData correlationData) throws AmqpException { this.send(exchange, routingKey, this.convertMessageIfNecessary(obje…...