当前位置: 首页 > 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类是通过构造和析构直接实现锁的初始化和回收

相关文章:

  • Qt网络数据解析方法总结
  • HNUST湖南科技大学-嵌入式考试选择题题库(109道纠正详解版)
  • 【进程控制】
  • PAT第七题素数对猜想
  • (超级详细)发明专利撰写
  • 码蹄集——输入、输出格式题
  • ACL访问控制列表简单实验CISCO
  • Android 理清 Gradle、AGP、Groovy 和构建文件之间的关系
  • Java高频面试之并发编程-09
  • Gentex EDI 需求分析
  • 投资控股集团类网站建设公司有哪些:打造专业形象与高效沟通的桥梁
  • 【wpf】Treeview控件的另类展示效果
  • Spdlog 日志组件的安装及使用
  • Linux:进程间通信->共享内存
  • 封装el-autocomplete,接口调用
  • 蓝桥杯 11. 打印大X
  • 手搓传染病模型(SEIR)
  • 2025年AEJ SCI2区:增强麻雀搜索算法CERL-SSA+工业物联网感知通信,深度解析+性能实测
  • 视觉导航中的滑动窗口
  • C++ RAII
  • 美乌总统梵蒂冈会谈,外交部:望有关各方继续通过对话谈判解决危机
  • 北美票房|《罪人》遭媒体唱衰,好莱坞业内人士集体反击
  • 北上广深还是小城之春?“五一”想好去哪玩了吗
  • 哈马斯同意释放剩余所有以色列方面被扣押人员,以换取停火五年
  • 谢震业、梁小静等名将在列,世界田联接力赛中国队名单出炉
  • ​王毅会见塔吉克斯坦外长穆赫里丁