Linux线程互斥锁
目录
🚩看现象,说原因
🚩解决方案
🚩互斥锁
🚀关于互斥锁的理解
🚀关于原子性的理解
🚀如何理解加锁和解锁是原子的
🚩对互斥锁的简单封装
引言
大家有任何疑问,可以在评论区留言或者私信我,我一定尽力解答。
今天我们学习Linux线程互斥的话题。Linux同步和互斥是Linux线程学习的延伸。但这部分挺有难度的,请大家做好准备。那我们就正式开始了。
🚩看现象,说原因
我们先上一段代码:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<cassert>
using namespace std;
int NUM=5;
int ticket=1000;
class pthread
{
public:char buffer[1024];pthread_t id;
};
void *get_ticket(void *args)
{pthread *pth=static_cast<pthread*>(args);while(1){usleep(1234);if(ticket<0){return nullptr;}cout<<pth->buffer<<" is ruuing ticket: "<<ticket<<endl;ticket--;}
}int main()
{vector<pthread*> pthpool;for(int i=0;i<NUM;i++){pthread* new_pth=new pthread();snprintf(new_pth->buffer,sizeof (new_pth->buffer),"thread-%d",i+1);int n=pthread_create(&(new_pth->id),nullptr,get_ticket,new_pth);assert(n==0);(void)n;pthpool.push_back(new_pth);}for(int i=0;i<pthpool.size();i++){int m= pthread_join(pthpool[i]->id,nullptr);assert(m==0);(void)m;}return 0;}
这段代码模拟的是抢票模型,一共有一千张票,我们让几个线程同时去抢票。看看有什么不符合实际的情况发生。
还真有不符合实际的情况发生:竟然抢到了负票。卧槽,这是什么情况,我们赶紧分析一下。
首先,在代码中我们定义了一个全局变量:ticket 。这个变量被所有线程所共享。
对于这种情形,我们直接拉向极端情况:假设此时的票数只有一张了。一个线程进入if内部,但是对票数还没有进行操作,这时,时间片到了,这个线程被切了下去。紧接着,一个线程就通过if判断,顺利抢到了最后一张票,对票数进行了操作。此时已经无票可抢了。这时,那个被切下来的线程又带着它的数据开始了抢票。但是在这个线程看来,票数依旧还有最后一张,所以,它又对票数进行了减减操作,得到了负票。
这种情况显然是不合理的,假如一个电影院有100个座位,结果卖出去102张票,这怎么可以呢?
我们定义的全局变量,在没有保护的情况下,往往晒不安全的。像上面多个线程在交替执行时造成的数据安全问题,我们称之为出现了数据不一致问题。
这就是个坑啊,必须解决。
🚩解决方案
在提出解决方案之前,我们先回顾几个概念。
- 多个执行流进行安全访问的共享资源,叫做临界资源
- 我们把多个执行流中,访问临界资源的代码叫做临界区,临界区往往是线程代码很小的一部分。
- 想让多个线程串行访问共享资源的方式叫做互斥。
- 对一个资源进行访问的时候,要么不做,要么做完,这种特性叫做原子性。一个对资源进行的操作,如果只有一挑汇编语句完成,那么就是原子的,反之就不是原则的。这是当前我们对原子性的理解,后面还会发生改变。
我们提出的解决方案就是加锁。相信大家第一次听到锁。对于什么是锁,如何加锁,锁的原理是什么我们都不清楚,别着急,我们在接下来的内容里会进行详细的详解。
我们先使用一下锁,见见猪跑!!
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<cassert>
using namespace std;
int NUM=5;
int ticket=1000;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
class pthread
{
public:char buffer[1024];pthread_t id;
};
void *get_ticket(void *args)
{pthread *pth=static_cast<pthread*>(args);while(1){ pthread_mutex_lock(&mutex);usleep(1234);if(ticket<0){ pthread_mutex_unlock(&mutex);return nullptr;}cout<<pth->buffer<<" is ruuing ticket: "<<ticket<<endl;ticket--;pthread_mutex_unlock(&mutex);}
}int main()
{vector<pthread*> pthpool;for(int i=0;i<NUM;i++){pthread* new_pth=new pthread();snprintf(new_pth->buffer,sizeof (new_pth->buffer),"user-%d",i+1);int n=pthread_create(&(new_pth->id),nullptr,get_ticket,new_pth);assert(n==0);(void)n;pthpool.push_back(new_pth);}for(int i=0;i<pthpool.size();i++){int m= pthread_join(pthpool[i]->id,nullptr);assert(m==0);(void)m;}return 0;}
结果显示抢票的过程非常顺利,接下来,我们把重心指向锁。
🚩互斥锁
首先,我们先认识一些锁的常见接口
// 所有锁的相关操作函数都在这个头文件下
//这些函数如果又返回值,操作成功的话,返回0,失败的话。返回错误码。错误原因被设置
#include <pthread.h>
// 锁的类型,用来创建锁
pthread_mutex_t
// 对锁进行初始化,第二个参数一般设位null
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
// 如果这个锁没有用了,可以调用该函数对锁进行销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 如果创建的锁是全局变量,可以这样初始化。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 对特定代码部分进行上锁,这部分代码只能有一次只能有一个执行流进入,被保护的资源叫做临界资源。
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 尝试上锁,不一定成功。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 取消锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
刚刚,我们已经使用一种方式实现了加锁,接下来,我们用另一种方式:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cassert>
using namespace std;
int NUM = 5;
int ticket = 1000;
class Thread_Data
{
public:Thread_Data(string name,pthread_mutex_t* mutex):_name(name),_mutex(mutex){}~Thread_Data(){}
public:string _name;pthread_mutex_t* _mutex;
};
void *get_ticket(void *args)
{Thread_Data *pth = static_cast<Thread_Data*>(args);while (1){pthread_mutex_lock(pth->_mutex); if (ticket > 0){ usleep(1234); cout << pth->_name << " is ruuing ticket: " << ticket << endl; ticket--;pthread_mutex_unlock(pth->_mutex); } else{pthread_mutex_unlock(pth->_mutex);break;}}
}int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);vector<pthread_t> tids(NUM); for (int i = 0; i < NUM; i++){char buffer[1024];Thread_Data *td=new Thread_Data(buffer,&mutex);snprintf(buffer, sizeof(buffer), "user-%d", i + 1);int n =pthread_create(&tids[i], nullptr, get_ticket, td);assert(n == 0);(void)n;}for (int i = 0; i < tids.size(); i++){int m = pthread_join(tids[i], nullptr);assert(m == 0);(void)m;}return 0;
}
运行一下,发现一直是4号线程在跑,其他线程呢?我也没让其他线程退出呀!而且抢票的时间变长了。
- 加锁和解锁是多个线程串行进行的,所以程序允许起来会变得很慢。
- 锁只规定互斥访问,没有规定谁优先访问。
- 锁就是让多个线程公平竞争的结果,强者胜出嘛。
🚀关于互斥锁的理解
- 所有的执行流都可以访问这一把锁,所以锁是一个共享资源。
- 加锁和解锁的过程必须是原子的,不会存在中间状态。要么成功,要么失败。加锁的过程必须是安全的。
- 谁持有锁,谁进入临界区。
如果一个执行流申请锁成功,继续向后运行;如果申请失败的话,这个执行流怎么办?
这种情况试一试不就知道了。我们依旧使用上面的一份代码,稍稍做一下修改:
所以,当一个执行流申请锁失败时,这个执行流会阻塞在这里。
🚀关于原子性的理解
如图,三个执行流
问:如果线程1申请锁成功,进入临界资源,正在访问临界资源区的时候,其他线程在做什么?
答:都在阻塞等待,直到持有锁的线程释放锁。
问; 如果线程1申请锁成功,进入临界资源,正在访问临界资源区的时候,可不可以被切换?
答:绝对是可以的,CPU管你有没有锁呢,时间片到了你必须下来。当持有锁的线程被切下来的时候,
是抱着锁走的,即使自己被切走了,其他线程依旧无法申请锁成功,也就无法继续向后执行。
这就叫作:江湖上没有我,但依旧有我的传说。
所以对于其他线程而言,有意义的锁的状态,无非两种:①申请锁前,②释放锁后
所以,站在其他线程的角度来看待当前持有锁的过程,就是原子的。
所以,未来我们在使用锁的时候,要遵守什么样的原则呢?
- 一定要保证代码的粒度(锁要保护的代码的多少i)要非常小。
- 加锁是程序员的行为,必须做到要加的话所有的线程必须要加锁。
🚀如何理解加锁和解锁是原子的
在分析如何实现加锁和解锁之前,我们先形成几个共识:
- CPU内执行流只有一套,且被所有执行流所共享。
- CPU内寄存器的内容属线程所有,是每个执行流的上下文。时间片到达,数据带走。
- 在进行加锁和解锁的时候,这个线程随时会因时间片已到而被换下来。
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性 。
如图:
我们假设有线程A,B两个线程,A想要获得锁
锁内存储的数据就是int类型的1。 A线程中有数字0。
①:movb $0,%al:将线程A中的1move到寄存器中。此时,是有可能发生时间片到达的,但是寄存器内的数据属于线程A,线程A是要带走的。
②:xchgb %al,mutex:将锁中的数据和寄存器内的数据进行交换。此时寄存器内的数据变成1,锁中的数据变为0。这是关键的一步,也有可能会发生切换。假设不巧的很,A线程被切下去了,B线程被切上来了。B线程从第一步开始,走到现在,寄存器内的数据应该是0。然后进入判断体eles进行挂起等待。
③如果在第二步中线程A被切下来,等待一段时间,时间片再次轮到线程A时,A将自己的数据加载到寄存器内进入判断,然后获得锁。
交换的过程由一条汇编构成
交换的本质:共享的数据,交换到线程的上下文中。
那么。如何完成解锁的操作呢。解锁的操作特别简单,只需一步。
将寄存器内的1归还给锁。然后return返回就可以了。
🚩对互斥锁的简单封装
相信大家对互斥锁都有了充分的了解。接下来,我们就实现一下对互斥锁的简单封装。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cassert>class Mutex
{
public:Mutex(pthread_mutex_t *mutex) : _mutex(mutex){}void unlock(){if (_mutex){pthread_mutex_unlock(_mutex);}}void lock(){if(_mutex){pthread_mutex_lock(_mutex);}}~Mutex(){}public:pthread_mutex_t *_mutex;
};
class Lockguard
{
public:Lockguard(Mutex mutex) : _mutex(mutex){_mutex.lock();}~Lockguard(){_mutex.unlock();}public:Mutex _mutex;
};
这种利用变量出了函数作用域自动销毁的性质,我们称之为RAII特性。
到这里,我们本篇的内容也就结束了,我们期待下一期博客相遇。
相关文章:
Linux线程互斥锁
目录 🚩看现象,说原因 🚩解决方案 🚩互斥锁 🚀关于互斥锁的理解 🚀关于原子性的理解 🚀如何理解加锁和解锁是原子的 🚩对互斥锁的简单封装 引言 大家有任何疑问,可…...
展开说说:Android列表之RecyclerView
RecyclerView 它是从Android5.0出现的全新列表组件,更加强大和灵活。用于显示列表形式 (list) 或者网格形式 (grid) 的数据,替代ListView和GridView成为Android主流的列表组件。可以说Android客户端只要有表格的地方就有RecyclerView。 RecyclerView 内…...
等保2.0时,最常见的挑战是什么?
等保2.0的常见挑战 等保2.0(网络安全等级保护2.0)是中国网络安全领域的基本制度,它对信息系统进行分级分类、安全保护和安全测评,以提高信息系统的安全性和可信性。在等保2.0的实施过程中,企业和组织面临多方面的挑战&…...
基于Vue 3.x与TypeScript的PPTIST本地部署与无公网IP远程演示文稿
文章目录 前言1. 本地安装PPTist2. PPTist 使用介绍3. 安装Cpolar内网穿透4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows系统环境本地部署开源在线演示文稿应用PPTist,并结合cpolar内网穿透工具实现随时随地远程访问与使用该项目。 PPTist …...
PHP的基本语法有哪些?
PHP的基本语法包括以下几个方面: PHP标记:PHP脚本以<?php开始,以?>结束。这是PHP文件的默认文件扩展名是.php。 变量和常量:变量以$符号开头,其后是变量的名称。常量使用define()函数定义,例如def…...
CSS的媒体查询:响应式布局的利器
关于CSS的媒体查询 CSS媒体查询是CSS层叠样式表(Cascading Style Sheets)中的一个核心功能,它使得开发者能够根据不同的设备特性和环境条件来应用不同的样式规则。这是实现响应式网页设计的关键技术,确保网站或应用能够在多种设备上,包括桌面…...
汇聚荣做拼多多运营第一步是什么?
汇聚荣做拼多多运营第一步是什么?在众多电商平台中,拼多多凭借其独特的社交电商模式迅速崛起,吸引了大量消费者和商家的目光。对于希望在拼多多上开店的商家而言,了解如何进行有效运营是成功的关键。那么,汇聚荣做拼多多运营的第…...
NeRF从入门到放弃4: NeuRAD-针对自动驾驶场景的优化
NeuRAD: Neural Rendering for Autonomous Driving 非常值得学习的一篇文章,几乎把自动驾驶场景下所有的优化都加上了,并且也开源了。 和Unisim做了对比,指出Unisim使用lidar指导采样的问题是lidar的垂直FOV有限,高处的东西打不…...
docker环境部署ruoyi系统前后端分离项目
创建局域网 docker network create net-ry 安装Redis 1 安装 创建两个目录 mkdir -p /data/redis/{conf,data} 上传redis.conf文件到/data/redis/conf文件夹中 cd /data/redis/conf 3.2 配置redis.conf文件 配置redis.conf文件: redis.conf文件配置注意&…...
UI(二)控件
文章目录 PatternLockProgressQRCodeRadioRatingRichTextScollBarSearchSelectSlideSpanStepper和StepperItemTextTextAreaTextClockTextInputTextPickerTextTimerTimePickerToggleWeb PatternLock PatternLock是图案密码锁组件,以九宫格图案的方式输入密码&#x…...
【图像分类】Yolov8 完整教程 |分类 |计算机视觉
目标:用YOLOV8进行图像分类。 图像分类器。 学习资源:https://www.youtube.com/watch?vZ-65nqxUdl4 努力的小巴掌 记录计算机视觉学习道路上的所思所得。 1、文件结构化 划分数据集:train,val,test 知道怎么划分数据集很重要。 文件夹…...
PyCharm 2024.1最新变化
PyCharm 2024.1 版本带来了一系列激动人心的新功能和改进,以下是一些主要的更新亮点: Hugging Face 模型和数据集文档预览:在 PyCharm 内部快速获取 Hugging Face 模型或数据集的详细信息,通过鼠标悬停或使用 F1 键打开文档工具窗口来预览。 …...
金融行业专题|某头部期货基于 K8s 原生存储构建自服务数据库云平台
为了进一步提升资源交付效率,不少用户都将数据库应用从物理环境迁移到容器环境。而对于 Kubernetes 部署环境,用户不仅需要考虑数据库在性能方面的需求,还要为数据存储提供更安全、可靠的高可用保障。 近期,某头部期货机构基于 S…...
DELL服务器 OpenManage监控指标解读
监控易是一款专业的IT基础设施监控软件,通过SNMP等多种方式,实时监控服务器、网络设备等IT资源的各项性能指标。对于DELL服务器 OpenManage,监控易提供了全面的监控解决方案,确保服务器的稳定运行。 一、网络连通性监控ÿ…...
vscode下无法识别node、npm的问题
node : 无法将“node”项识别为 cmdlet、函数、脚本文件或可运行程序的名称 因为node是在cmd安装的,是全局安装的,并不是在这个项目里安装的。 解决方案: 1.在vscode的控制台,针对一个项目安装特定版本的node; 2.已经…...
C语言之字符串处理函数
文章目录 1 字符串处理函数1.1 输入输出1.1.1 输出函数puts1.1.2 输入函数gets 1.2 连接函数1.2.1 stract1.2.2 strncat 1.3 复制1.3.1 复制strcpy1.3.2 复制strncpy1.3.3 复制memcpy1.3.4 指定复制memmove1.3.5 指定复制memset1.3.6 新建复制strdup1.3.7 字符串设定strset 1.4…...
昇思25天学习打卡营第4天|onereal
今天学习的内容是:ResNet50迁移学习 以下内容拷贝至教程,实话实话看不懂,迷迷糊糊都运行jupyter里的代码。走完程序,训练生成了一些图片。 ResNet50迁移学习 在实际应用场景中,由于训练数据集不足,所以很少…...
restTemplate使用总结
1、配置类 Configuration public class RestTemplateConfig() {Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory factory) {return new RestTemplate(factory);}Beanpublic ClientHttpRequestFactory simpleClientHttpRequestFactory() {HttpComponentsClient…...
【云服务器介绍】选择指南 腾讯云 阿里云全配置对比 搭建web 个人开发 app 游戏服务器
省流目录:适用于博客建站(2-4G)、个人开发/小型游戏[传奇/我的世界/饥荒](4-8G)、数据分析/大型游戏[幻兽帕鲁/雾锁王国]服务器(16-64G) 1.京东云-618专属活动 官方采购季专属活动地址&#x…...
PostgreSQL 高级SQL查询(三)
1. JOIN 操作 1.1 内连接(INNER JOIN) 内连接用于返回两个表中存在匹配关系的记录。基本语法如下: SELECT columns FROM table1 INNER JOIN table2 ON table1.column table2.column;例如,从 users 表和 orders 表中检索所有用…...
麒麟系统安装Redis
一、背景 如前文(《麒麟系统安装MySQL》)所述。 二、下载Redis源码 官方未提供麒麟系统的Redis软件,须下载源码编译。 下载地址:https://redis.io/downloads 6.2.14版本源码下载地址:https://download.redis.io/re…...
Java-方法引用
方法引用概念 把已经有的方法拿过来用,当做函数式接口中抽象方法的方法体 前提条件 1、引用处必须是函数式接口 2、被引用的方法必须已经存在 3、被引用方法的形参和返回值 需要跟抽象方法保持一致 4、被引用方法的功能要满足当前需求 方法引用格式示例 方…...
华为---配置基本的访问控制列表(ACL)
11、访问控制列表(ACL) 11.1 配置基本的访问控制列表 11.1.1 原理概述 访问控制列表ACL(Access Control List)是由permit或deny语句组成的一系列有顺序的规则集合,这些规则根据数据包的源地址、目的地址、源端口、目的端口等信息来描述。A…...
Apple Intelligence,我们能得到什么?(上)
苹果公司WWDC 2024发布会,苹果AI成为最吸睛的焦点。不过,苹果的AI不是大家口中的AI,而是苹果独有的概念:Apple Intelligence,苹果智能。 所谓Apple Intelligence,被定义为iPhone、iPad和Mac的个人智能系统…...
【数据库中的存储桶】
存储桶是对象存储系统中的一个核心概念,起源于Amazon S3(Simple Storage Service)并被其他对象存储解决方案(如MinIO、Google Cloud Storage等)广泛采用。在传统的文件系统中,我们通常使用目录和子目录来组…...
多选项卡的shiny
下面是一个包含多个选项卡的 Shiny 应用程序示例代码。在这个例子中,我们创建了一个包含三个选项卡的 Shiny 应用程序,每个选项卡中都有不同的内容。 library(shiny)# Define UI ui <- fluidPage(titlePanel("多选项卡 Shiny 应用"),tabse…...
Python项目Django框架发布相关
1.Nginx配置 server { listen 80; server_name 域名地址;location / { uwsgi_pass 0.0.0.0:4563;// 运行地址include uwsgi_params;} location /static{ // 静态文件路径alias /www/wwwroot/djserverproject/static;}}server { listen 443; server_name 域名地址;ssl_certific…...
kettle使用手册 安装9.0版本 建议设置为英语
0.新建转换的常用组件 0. Generate rows 定义一个字符串 name value就是字符串的值 0.1 String operations 字段转大写 去空格 1. Json input 来源于一个json文件 1.json 或mq接收到的data内容是json字符串 2. Json output 定义Jsonbloc值为 data, 左侧Fieldname是数据库…...
golang string、byte[]以及rune的基本概念,用法以及区别
在 Go 语言中,string、byte[] 和 rune 是处理文本和字符的三种不同数据类型。它们有各自的用途和特点,下面将详细介绍它们的基本概念、用法以及区别。 1. string 基本概念 字符串类型:string 是 Go 语言中的一种基本类型,用于表…...
全国211大学名单及排名
序号 名称 省份 985 211 双一流 1 北京大学 北京 是 是 是 2 清华大学 北京 是 是 是 3 复旦大学 上海 是 是 是 4 上海交通大学 上海 是 是 是 5 浙江大学 浙江 是 是 是 6 国防科技大学 湖南 是 是 是 7 中国人民大学 北京 是 …...
国内优秀企业网站/帆软社区app
最近情人节要来了也,翻出我以前的博客~有空附上完整的代码...
国外wordpress cms主题/人民日报评网络暴力
1.计算机病毒的结构主要包括()模块。 A.传染 B.触发 C.破坏 D.复制 2.与专用会计核算软件相比较,通用会计核算软件的优点有()。 A.成本相对较低 B.维护量小且维护有保障 C.开发者决定系统的扩充与修改 D.软件开发水…...
什么网站可以自己接工程做预算/网站建设推广多少钱
数组小谈😁 庆哥: 嗨,小白,知道啥是数组吗?😎 小白: 你看你这话说的,数组那还不简单,学计算机的没有不知道数组的吧,我们刚开始接触C语言的时候就有数组啊,现在在学习java,也有数组啊,一般不就这样嘛😁 int[] array = new int[10]这就创建了一个长度为10的…...
cn域名注册/网络推广优化品牌公司
关于 http://openresty.org/cn/about.html 这个开源 Web 平台主要由章亦春(agentzh)维护。在 2011 年之前曾由淘宝网赞助,在后来的 2012 ~ 2016 年间主要由美国的 CloudFlare 公司 提供支持。目前,OpenResty 主要由 OpenResty 软件…...
定位网站关键词/深圳seo推广公司
一、/boot下面的文件全部丢失:( grub ,kernel,initrd ramdisk) 1.用bootdisk启动,进入liunx rescue模式,选择local install或nfs (http)安装方式 2.进入修模模式: (1). cd /mnt/sysp_w_picpath 查看有些什么文件(假如是空的) (2) install kernel: cd /mnt/source/Server rpm -i…...
网站建设验收标准/网站seo系统
刊号 # 28 - Oct 05, 2008 项目新闻 准备翻译:NetBeans 6.5 本地化 NetBeans 社区成员正在准备 NetBeans 6.5的翻译:新的 L10N(本地化)工具已经准备好了。语言团队以前工作与老的工具上,将需要更新或者重新做一些文件。…...