当前位置: 首页 > news >正文

Linux——线程(2)线程互斥(锁)

知识回顾

在学习本篇内容前,我们需要先回顾一下几个概念。

临界资源:多线程执行流共享的资源就叫做临界资源

临界区:每个线程内部,访问临界资源的代码,就叫做临界区 

互斥:任何时刻,互斥保证有且只有⼀个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量 归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。此时如果多个线程并发操作共享变量,可能会有一些问题。因此,我们需要理解互斥与同步的原理并对共享资源在合适的时候“加锁”。

我们所保护的,并不是所谓的共享资源,而是称为临界区的代码。在临界区内,我们通过加锁只允许一个线程执行。

加锁一定不能大块代码地进行加锁,只要把临界区的一部分加锁即可。

一、不加锁会出现什么问题

这里我们写了一个利用线程进行抢票功能的代码(线程封装是作者自己写的这里直接拿来用,实际中直接调用pthread_create即可)

#include <iostream>
#include "mythread.hpp"
#include <vector>
using namespace ThreadModule;#define NUM 4
int ticket = 10000; // 共享资源
void Ticket()
{while (true){if (ticket > 0){usleep(1000);// 抢票printf("get ticket success ! ticketnum:%d\n", ticket--);}else{break;}}
}int main()
{// 构建线程对象std::vector<Thread> threads;for (int i = 0; i < NUM; i++){threads.emplace_back(Ticket);}// 启动线程for (auto &thread : threads){thread.Start();}// 等待for (auto &thread : threads){thread.Join();}
}

按道理来说票数为正就会一直抢,直到0所有进程就会退出抢票,但我们运行时发现,票数居然出现负数了?!这是为什么呢?

这就需要我们最上面的概念——原子性,在ticket--的过程中一共有三步:把ticket从内存放在CPU,CPU进行--,再放回内存,这是对于每一个线程的过程,但是在这个过程中,如果我们把数据放进CPU那么这个数据就对于线程是私有的了(本身的ticket是全局变量所以共享资源),他们完成第一步时,这时线程突然切换了,结果到下一个线程本来应该拿到比上个线程少一的ticket,但因为上一个还没有--所以拿到的值是一样的,然后当他们都进行--,就会出现为负数的原因。所以,--操作并不是原子性的,(if也不是)我们要加锁本质就是不让线程在完成这些操作的过程中被切换。

二、关于锁

1.锁长什么样

我们可以定义一个pthread_mutex_t类型(pthread库中提供的类型)的变量,这就是一把锁,第二个接口就是对锁进行初始化。但如果我们定义的锁是全局或静态的,就可以用最下面的宏进行初始化(这种锁就不需要destroy)。

2.怎么加锁与解锁

我们只需要把锁的地址传进来就行了。

所以现在上面的情况就可以解决了。

pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
int ticket = 10000; // 共享资源
void Ticket()
{while (true){pthread_mutex_lock(&lock);if (ticket > 0){usleep(1000);// 抢票printf("get ticket success ! ticketnum:%d\n", ticket--);pthread_mutex_unlock(&lock);}else{pthread_mutex_unlock(&lock);break;}}
}

只要线程启动就要保证原子性,完成后进行解锁。

锁作为全局变量,保护着全局变量的资源(临界资源),可是谁来保证锁的安全,锁只需要保证原子性即可。

线程在申请锁的时候,其他线程要进行阻塞等待!线程在访问临界区代码时,是可以切换的,但在加锁的线程被切走的时候,其他线程是无法进来访问临界区的,该线程要么不做,要么做完!这对于其他线程就保证了原子性。

其实,锁这个概念就是线程互斥,就是为了让线程在完成任务的过程中不让其他线程“加入”等自己完成后再让他们执行。

三、锁的封装

这里无非就是利用lock和unlock所以直接上代码

#pragma once
#include <iostream>namespace LockModule
{class Mutex{public:Mutex(const Mutex&)=delete;//需要保证多线程用一把锁,不允许赋值和拷贝const Mutex& operator =(const Mutex&)=delete;Mutex(){int n=::pthread_mutex_init(&_lock,nullptr);(void)n;}~Mutex(){int n=::pthread_mutex_destroy(&_lock);(void)n;}void Lock(){int n=::pthread_mutex_lock(&_lock);(void)n;}void Unlock(){int n=::pthread_mutex_unlock(&_lock);(void)n;}private:pthread_mutex_t _lock;};class LockGuard{public:LockGuard(Mutex&mtx):_mtx(mtx){_mtx.Lock();}~LockGuard(){_mtx.Unlock();}private:Mutex& _mtx;};
}

封装lockguard类是通过构造和析构直接实现锁的初始化和回收

http://www.xdnf.cn/news/184717.html

相关文章:

  • 机器学习 | 基于回归模型的交通需求预测案例分析及代码示例
  • 日本IT|UIUX主要的工作都是哪些?及职业前景
  • 【每日随笔】文化属性 ② ( 高维度信息处理 | 强者思维形成 | 认知重构 | 资源捕获 | 进化路径 )
  • LangChain构建大模型应用之RAG
  • 使用ROS实现多机通讯
  • 线上查询车辆出险记录:快速掌握事故情况!
  • 大模型API密钥的环境变量配置(大模型API KEY管理)(将密钥存储在环境变量)(python-dotenv)(密钥管理)
  • 数据结构(七)---链式栈
  • AI看论文自动生成代码库:Paper2Code如何革新科研复现?
  • 函数式链表:Python编程的非常规 “链” 接
  • QT6 源(53)篇三:存储 c 语言字符串的类 QByteArray 的使用举例,
  • 移除生产环境所有console.log
  • 给视频自动打字幕:从Humanoid-X、UH-1到首个人形VLA Humanoid-VLA:迈向整合第一人称视角的通用人形控制
  • 基于STM32、HAL库的AD7616BSTZ模数转换器ADC驱动程序设计
  • Linux操作系统学习---进程地址空间
  • 【LaTex】8.1 文档类与层级
  • 前端权限管理
  • 小刚说C语言刷题——1320时钟旋转
  • 生成式人工智能认证(GAI认证)要学哪些知识?
  • google chrome 中 fcitx5 候选框不跟随光标
  • 【SpringCloudAlibaba】Dubbo 和 Spring Cloud OpenFeign 在服务治理能力上的差异
  • 生成式人工智能认证(GAI认证)考试难吗?
  • SpringBoot的自动扫描特性-笔记
  • Vue初步总结-摘自 黑马程序员
  • 浅谈 MySQL 日志的原理
  • 新增 29 个专业,科技成为关键赛道!
  • 互联网的下一代脉搏:深入理解 QUIC 协议
  • Day23-Web开发——Linux
  • 基于深度学习的图像压缩技术(二)
  • AI时代下如何实现财务自由?