【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:本人小白,笔记均在个人理解基础上整理,…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
