【Linux】线程安全及锁的使用

文章目录

  • 前言
  • 一、锁
    • 1.定义一个锁变量
    • 2.pthread_mutex_init
    • 3.pthread_mutex_destroy
    • 4.pthread_mutex_lock/pthread_mutex_unlock
    • 5.静态变量锁和全局变量锁的初始化
  • 二、问题描述及锁的运用
  • 三、RAII风格的锁


前言

临界资源: 在多个线程或进程间共享的资源.
临界区: 代码中访问临界资源的那部分代码区域.
多个线程同时访问共享数据, 其中至少一个线程进行了写操作, 且没有适当的同步机制来保护数据, 可能导致数据的不一致性, 也就是一种线程安全问题.

一、锁

多个线程同时访问共享数据, 其中至少一个线程进行了写操作, 且没有适当的同步机制来保护数据, 可能导致数据的不一致性, 对于这种问题, 可以通过加锁来解决, 加锁后, 只有申请到锁的线程才能进入临界区执行代码语句, 其他没有竞争到锁的线程会阻塞等待, 直到申请到锁的线程将锁释放, 接着各个线程再来竞争锁.
相当于被加锁的代码区域只能被各个线程串行执行.

1.定义一个锁变量

头文件: #include <pthread.h>

pthread_mutex_t mutex;

2.pthread_mutex_init

头文件: #include <pthread.h>
函数声明: int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

  • 返回值: 如果成功, 返回值为 0, 如果失败, 返回一个非零错误码.
  • mutex: 指向要初始化的互斥锁的指针.
  • attr: 设置互斥锁的属性, 一般传递 nullptr, 表示使用默认属性.

功能: 初始化锁.

示例代码:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, nullptr);

3.pthread_mutex_destroy

头文件: #include <pthread.h>
函数声明: int pthread_mutex_destroy(pthread_mutex_t *mutex);

  • 返回值: 如果成功, 返回值为 0, 如果失败, 返回一个非零错误码.
  • mutex: 指向要释放的互斥锁的指针.

功能: 释放锁.

示例代码:

pthread_mutex_t mutex;
pthread_mutex_destroy(&mutex);

4.pthread_mutex_lock/pthread_mutex_unlock

头文件: #include <pthread.h>
函数声明: int pthread_mutex_lock(pthread_mutex_t *mutex); / int pthread_mutex_unlock(pthread_mutex_t *mutex);

  • 返回值: 如果成功, 返回值为 0, 如果失败, 返回一个非零错误码.
  • mutex: 指向已初始化的互斥锁的指针.

功能: 加锁/解锁.

示例代码:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, nullptr);
pthread_mutex_lock(&mutex);
//...
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);

在 pthread_mutex_lock(&mutex); 和 pthread_mutex_unlock(&mutex); 之间的代码就是被加锁的代码, 只有申请到锁的线程才能进入执行, 释放锁后, 各个线程再重新竞争锁.

5.静态变量锁和全局变量锁的初始化

如果锁变量是 static 修饰的静态变量锁或者是声明在全局的锁, 可以直接通过宏进行初始化:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

二、问题描述及锁的运用

假设多个线程的入口函数为同一个, 代码如下:

#include <iostream>
#include <pthread.h>
using namespace std;void* Routine(void* arg)
{int cnt = 0;for(int i = 0; i < 3; ++i){printf("线程[%lld]中的cnt: %d, 地址为: 0x%x\n", pthread_self(), ++cnt, &cnt);}cout << endl;
}int main()
{pthread_t tids[3];for(int i = 0; i < 3; ++i){pthread_create(tids + i, nullptr, Routine, nullptr);}for(int i = 0; i < 3; ++i){pthread_join(tids[i], nullptr);}return 0;
}

可以看到, 在入口函数中, 存在一个变量 cnt, 那么多个线程的入口函数都是同一个, 每个线程都对该入口函数里的 cnt 进行累加操作, 是否会出现线程安全问题呢?

运行结果:
在这里插入图片描述
这里是不存在线程安全问题的, 因为每个线程在线程库中都存在自己的结构及数据上下文:
在这里插入图片描述
所以在入口函数中的变量等数据都是每个线程私有的一部分, 因此并不会有线程安全问题.

但是如果是全局变量或者被 static 修饰的变量, 被多个线程同时访问修改时就会存在线程安全问题, 假设一个场景: 三个线程进行"抢票", 即对全局变量 tickets 进行减减操作, 代码如下:

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;int tickets = 30; //总票数void* Ticket(void* arg)
{while(1){//还有票if(tickets > 0){usleep(100000); //休眠0.1秒,模拟抢票前戏printf("线程 0x%x 抢到了一张票,还剩 %d 张票...\n", pthread_self(), --tickets);}else{break;}}return nullptr;
}int main()
{pthread_t tids[3];for(int i = 0; i < 3; ++i){pthread_create(tids + i, nullptr, Ticket, nullptr);}for(int i = 0; i < 3; ++i){pthread_join(tids[i], nullptr);}return 0;
}

运行结果:
在这里插入图片描述
可以看到, 在不加锁的情况下, tickets 直接被干到负数了, 原因就在于当 tickets 只剩 1 张时, 进行 if(tickets > 0) 判断时, 三个线程并行的执行了判断且进入了 if 语句内, 那 tickets 被干到负数也是必然的了.

通过加锁来规避这个问题, 代码修改如下:

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;int tickets = 10000; //总票数
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* Ticket(void* arg)
{while(1){pthread_mutex_lock(&mutex);//还有票if(tickets > 0){usleep(50000); //休眠0.05秒,模拟抢票前戏printf("线程 0x%x 抢到了一张票,还剩 %d 张票...\n", pthread_self(), --tickets);pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}return nullptr;
}int main()
{pthread_t tids[3];for(int i = 0; i < 3; ++i){pthread_create(tids + i, nullptr, Ticket, nullptr);}for(int i = 0; i < 3; ++i){pthread_join(tids[i], nullptr);}pthread_mutex_destroy(&mutex);return 0;
}

需要注意的是 if 和 else 语句中都需要进行解锁, 如果只在 if 中解锁, 最后申请到锁但走了 else 语句的线程就并没有释放锁, 可能导致其他在等待锁资源的线程永远阻塞申请不到锁.

运行结果:
在这里插入图片描述

三、RAII风格的锁

虽然加锁, 解锁配套使用不难, 但毕竟是人写的, 难免可能出现加锁后忘了解锁, 而后导致死锁的情况, 所以通过 RAII 设计风格封装一下锁的使用, 可以有效避免这种问题.

示例代码:

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;int tickets = 100; //总票数
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;class LockGuard 
{
public://构造加锁LockGuard(pthread_mutex_t* m): _pmutex(m){pthread_mutex_lock(_pmutex);}//析构解锁~LockGuard (){pthread_mutex_unlock(_pmutex);}
public:pthread_mutex_t* _pmutex;
};void* Ticket(void* arg)
{while(1){usleep(100000);LockGuard lg(&mutex);//还有票if(tickets > 0){usleep(50000); //休眠0.05秒,模拟抢票前戏printf("线程 0x%x 抢到了一张票,还剩 %d 张票...\n", pthread_self(), --tickets);}else{break;}}return nullptr;
}int main()
{pthread_t tids[3];for(int i = 0; i < 3; ++i){pthread_create(tids + i, nullptr, Ticket, nullptr);}for(int i = 0; i < 3; ++i){pthread_join(tids[i], nullptr);}pthread_mutex_destroy(&mutex);return 0;
}

运行结果:
在这里插入图片描述
这样可以达到, 构造时加锁, 析构时解锁, 全自动化, 不必担心忘了解锁了.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/17759.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5 分钟快速上手图形验证码,防止接口被恶意刷量!

5 分钟快速上手图形验证码&#xff0c;防止接口被恶意刷量&#xff01; 大家好&#xff0c;我是程序员小白条&#xff0c;今天来给大家介绍一个快速实现图形验证码的优秀框架 AJ-Captcha。 需求分析 如果注册接口没有验证码这种类型的限制&#xff0c;很容易会被刷量&#x…

protobuf —— 快速上手

protobuf —— 快速上手 创建 .proto 文件添加注释指定proto3语法package 声明符定义消息&#xff08;message&#xff09; 定义消息字段字段定义基本格式字段名称命名规范字段类型字段唯一编号示例 转换关系示例&#xff1a;增加姓名和年龄字段 字段唯一编号字段编号范围编码效…

英伟达SSD视觉算法,jetson.inference在jetson nano中部署

一、用官方镜像刷机 安装SD卡擦除工具SD Card Formatter https://www.sdcardformatter.com/download/ 格式化SD卡 下载官方镜像 https://developer.nvidia.com/jetson-nano-sd-card-image 安装刷机工具balenaEtcher https://www.balena.io/etcher 将上面下载的镜像压缩包解…

spark的简单学习二

一 spark sql基础 1.1 Dataframe 1.介绍&#xff1a; DataFrame也是一个分布式数据容器。然而DataFrame更像传统数据库的二维表 格&#xff0c;除了数据以外&#xff0c;还掌握数据的结构信息&#xff0c;即schema。同时&#xff0c;与Hive类似&#xff0c;DataFrame也支 持…

gin框架精通篇(二)

原生数据库使用 导入模块&#xff1a;go get -u github.com/go-sql-driver/mysql 安装 mysql 数据库 安装数据库可能遇到的问题&#xff1a;&#xff08;网上的方法基本可以解决&#xff09; ERROR 1045 (28000): Access denied for user ‘-root’‘localhost’ (using passwo…

HTML 页面布局

慢慢生活&#xff0c;慢慢变好 —— 24.5.28 页面布局 盒子: 页面中所有的元素(标签)&#xff0c;都可以看做是一个盒子&#xff0c;由盒子将页面中的元素包含在一个矩形区域内&#xff0c;通过盒子的视角更方便的进行页面布局 盒子模型组成: 内容区域(content)、内边距区域(pa…

React中useRef的使用

在 React 中&#xff0c;useRef 是一个钩子&#xff0c;用于返回一个可变的 ref 对象&#xff0c;其 .current 属性被初始化为传递的参数&#xff08;initialValue&#xff09;。这个返回的对象在组件的整个生命周期内保持不变。useRef 可以用来直接访问 DOM 元素或存储任何不会…

Oracle逻辑备库异构平台支持列表

关于oracle逻辑备库支持的OS平台列表&#xff0c;以及oracle DG主备版本支持情况&#xff0c;通过参考oracle 官方文档来做个整理总结&#xff0c;希望能帮到大家。 ​一.Oracle逻辑备库异构平台支持列表 oracle DG是否异构是通过platform_id来判断&#xff0c;通过查询v$dat…

UOS开通22端口用于SSH

主机虚拟机能相互ping通&#xff0c;但是mobaxterm等(远程ssh软件)连接不了 原因很有可能是虚拟机未安装SSH服务 检测是否已安装ssh服务&#xff0c;输入以下命令 ssh localhost 如果出现&#xff1a; ssh: connect to host localhost port 22: Connection refused 表明没有…

数据结构的希尔排序(c语言版)

一.希尔排序的概念 1.希尔排序的基本思想 希尔排序是一种基于插入排序算法的优化排序方法。它的基本思想如下: 选择一个增量序列 t1&#xff0c;t2&#xff0c;......&#xff0c;tk&#xff0c;其中 ti > tj, 当 i < j&#xff0c;并且 tk 1。 按增量序列个数k&#…

Centos安装,window、ubuntus双系统基础上安装Centos安装

文章目录 前言一、准备工作二、开始安装1、2、首先选择DATE&TIME2、选择最小安装3、 选择安装位置 总结 前言 因工作需要&#xff0c;我需要在工控机上额外装Centos7系统&#xff0c;不过我是装在机械硬盘上了不知道对性能是否有影响&#xff0c;若有影响&#xff0c;后面…

基于C#开发web网页管理系统模板流程-主界面管理员入库和出库功能完善

前言 紧接上篇->基于C#开发web网页管理系统模板流程-主界面管理员录入和编辑功能完善-CSDN博客 本篇将完善主界面的管理员入库和出库功能&#xff0c;同样的&#xff0c;管理员入库和出库的设计套路适用于动态表的录入和编辑 首先还是介绍一下本项目将要实现的功能 &#xf…

【sass数据类型简介以及使用方法】

Sass&#xff08;Syntactically Awesome Stylesheets&#xff09;是一种CSS预处理器&#xff0c;它允许你使用变量、嵌套规则、混合&#xff08;mixin&#xff09;、函数等功能来编写更易于维护和组织的CSS代码。Sass具有自己的数据类型系统&#xff0c;这些数据类型在编写Sass…

[Android]项目打包APK时报错PKCS12 keystore not in version 3 format

报错&#xff1a; PKCS12 keystore not in version 3 format Execution failed for task :app:packageRelease. > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable > com.android.ide.commo…

如何使用 FastAPI 部署 NLP 模型?

模型部署是将训练好的自然语言处理模型集成到生产环境中的过程。模型接收输入数据&#xff0c;预测输出。 有多种将 NLP 模型部署到生产环境的方法&#xff0c;包括 Flask、Django、Bottle 等框架。 本文将分享使用 FastAPI 构建和部署 NLP 模型。 在本文中&#xff0c;你将…

揭开Java序列化的神秘面纱(上)Serializable使用详解

Java序列化(Serialization)作为一项核心技术&#xff0c;在Java应用程序的多个领域都有着广泛的应用。无论是通过网络进行对象传输&#xff0c;还是实现对象的持久化存储&#xff0c;序列化都扮演着关键的角色。然而&#xff0c;这个看似简单的概念蕴含着丰富的原理和用法细节&…

如何为个人网站部署SSL安全证书,以实现网站的 HTTPS 加密协议访问?

哈喽&#xff0c;大家好呀&#xff01;这里是码农后端。完成了域名的备案与解析后&#xff0c;就可以通过域名来访问我们的网站了。本篇将介绍如何为我们的网站部署SSL安全证书&#xff0c;实现网站的 HTTPS 加密协议访问。 1、购买SSL证书 未进行SSL证书部署&#xff0c;访问网…

数据保存MySQL语法

文章目录 一、导包二、链接数据库三、创建操作工具【游标 cursor】四、执行命令1.创建表2.插入数据 五、数据库提交 一、导包 import pymysql二、链接数据库 db pymysql.connect(hostlocalhost,port3306,userroot,passwordroot123,dbpython案例,charsetutf8 )三、创建操作工…

回答篇二:测试开发高频面试题目

引用之前文章&#xff1a;测试开发高频面试题目 本篇文章是回答篇&#xff08;持续更新中&#xff09; 1. 在测试开发中使用哪些自动化测试工具和框架&#xff1f;介绍一下你对其中一个工具或框架的经验。 a. 测试中经常是用的自动化测试工具和框架有Selenium、Pytest、Postman…

调整表格大小

方法一&#xff1a;使用鼠标拖动表格边框或右下角的调整控点 在Word文档中&#xff0c;选中要缩小的表格&#xff0c;将鼠标指针放在表格的边框线上&#xff0c;直到指针变成双箭头的形状。 按住鼠标左键&#xff0c;拖动边框线&#xff0c;调整表格的宽度或高度。如果同时按住…