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

【Linux学习笔记】线程的同步与互斥(一)

【Linux学习笔记】线程的同步与互斥(一)

在这里插入图片描述

🔥个人主页大白的编程日记

🔥专栏Linux学习笔记


文章目录

  • 【Linux学习笔记】线程的同步与互斥(一)
    • 前言
  • 1. 线程同步与互斥
  • 1. 线程互斥
    • 1-1 进程线程间的互斥相关背景概念
    • 1-2 互斥量latex
    • 1-3 互斥量实现原理探究
    • 1-4 互斥量的封装
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了线程的概念与控制(三) 今天我们讲的是线程的同步与互斥(一)。话不多说,我们进入正题!向大厂冲锋!
在这里插入图片描述

1. 线程同步与互斥

1. 线程互斥

1-1 进程线程间的互斥相关背景概念

  • 共享资源
  • 临界资源:多线程执行流被保护的共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
    在这里插入图片描述

1-2 互斥量latex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
  • 多个线程并发的操作共享变量,会带来一些问题。
1 // 操作共享变量会有问题的售票系统代码  
2 #include<stdio.h>  
3 #include<stdio.h>  
4 #include <string.h>  
5 #include <unistd.h>  
6 #include <pthread.h>int ticket = 100;   
void \*route(void \*arg)   
{ char \*id  $=$  (char\*arg; while(1){ if ticket>0{ asleep(1000); printf("%s sells ticket:%d\n",id,ticket); ticket--; }else{ break; }    
}   
int main(void{ pthread_t t1,t2,t3,t4; pthread_create(&t1,NULL,route,(void\*"thread 1"); pthread_create(&t2,NULL,route,(void\*"thread 2"); pthread_create(&t3,NULL,route,(void\*"thread 3"); pthread_create(&t4,NULL,route,(void\*"thread 4")); pthread_join(t1,NULL); pthread_join(t2,NULL); pthread_join(t3,NULL); pthread_join(t4,NULL);   
}一次执行结果:```cpp
thread 4 sells ticket:100
...
...
thread 4 sells ticket:1
...
thread 2 sells ticket:0
...
thread 1 sells ticket:-1
...
thread 3 sells ticket:-2

为什么可能无法获得争取结果?

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • –ticket 操作本⾝就不是⼀个原子操作
1 取出ticket--部分的汇编代码  
2 objdump -d a.out > test.Objectdump  
3 152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket>  
4 153 400651: 83 e8 01 sub $0x1,%eax  
5 154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) #600b34 <ticket>

操作并不是原子操作,而是对应三条汇编指令:

  • load:将共享变量ticket从内存加载到寄存器中
  • update: 更新寄存器里面的值,执行-1操作
  • store:将新值,从寄存器写回共享变量ticket的内存地址

要解决以上问题,需要做到三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。

  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。

  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

  • 要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

在这里插入图片描述

互斥量的接口

初始化互斥量

初始化互斥量有两种方法:

  • 方法1,静态分配:
1 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • 方法2,动态分配:
1 intPTHread_mutex_init(pthread_mutex_t \*restrict mutex, constPTHread_mutexattr_t \*restrict attr);
2 参数:  
3 mutex: 要初始化的互斥量  
4 attr: NULL

销毁互斥量需要注意:

  • 使用 PTHREAD_MUTEXinitialized 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
1 int pthread_mutex_destroy(pthread_mutex_t * mutex);

互斥量加锁和解锁

1 intPTHread_mutex_lock(pthread_mutex_t \*_mutex);
2 intpthread_mutex_unlock(pthread_mutex_t * mutex);
3 返回值:成功返回0,失败返回错误号

调用pthread_lock时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

改进上面的售票系统:

1 #include<stdio.h>   
2 #include<stdlib.h>   
3 #include<string.h>   
4 #include<unistd.h>   
5 #include <pthread.h>   
6 #include <sched.h>   
7   
8 int ticket  $= 100$  ·   
9PTHread_mutex_t mutex;   
10   
11 void \*route(void \*arg)   
12 {   
13 char \*id  $=$  (char\*)arg;   
14 while(1{   
15 pthread_mutex_lock(&_mutex);   
16 if(ticket  $>0$  ){   
17 asleep(1000);   
18 printf("%s sells ticket:%d\n",id,ticket);
19 ticket--;   
20 pthread_mutex_unlock(& mutex);   
21 } else {   
22 pthread_mutex_unlock(& mutex);   
23 break;   
24 }   
25 }   
26 return nullptr;   
27 }   
28   
29 int main(void)   
30 {   
31 pthread_t t1, t2, t3, t4;   
32   
33 pthread_mutex_init(& mutex, NULL);   
34   
35 pthread_create(&t1, NULL, route, (void*)"thread 1");   
36 pthread_create(&t2, NULL, route, (void*)"thread 2");   
37 pthread_create(&t3, NULL, route, (void*)"thread 3");   
38 pthread_create(&t4, NULL, route, (void*)"thread 4");   
39   
40 pthread_join(t1, NULL);   
41 pthread_join(t2, NULL);   
42 pthread_join(t3, NULL);   
43 pthread_join(t4, NULL);   
44 pthread_mutex_destroy(& mutex);   
45 }

在这里插入图片描述

1-3 互斥量实现原理探究

  • 经过上面的例子,大家已经意识到单纯的 i++i++i++ 或者 ++i++i++i 都不是原子的,有可能会有数据一致性问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swapexchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。现在我们把lockunlock的伪代码改一下

lock:

movb $0, %al
xchgba, mutex
if(al寄存器的内容 > 0) {
return 0;
} else
挂起等待;
goto lock;

unlock:

movb $1, mutex
唤醒等待Mutex的线程;
return 0;

在这里插入图片描述
在这里插入图片描述

1-4 互斥量的封装

Lock.hpp

#pragma once#include <iostream>
#include <string>
#include <pthread.h>namespace LockModule
{// 对锁进行封装,可以独立使用class Mutex{public:// 删除不要的拷贝和赋值Mutex(const Mutex&) = delete;Mutex& operator=(const Mutex&) = delete;Mutex(){int n = pthread_mutex_init(&_mutex, nullptr);(void)n; // 忽略返回值}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n; // 忽略返回值}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n; // 忽略返回值}pthread_mutex_t* GetMutexOriginal() // 获取原始指针{return &_mutex;}~Mutex(){int n = pthread_mutex_destroy(&_mutex);(void)n; // 忽略返回值}private:pthread_mutex_t _mutex; // 注意这里应该是 _mutex 而不是 __mutex};// 采用RAII风格,进行锁管理class LockGuard{public:LockGuard(Mutex& mutex) : _mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex& _mutex; // 注意这里应该是 _mutex 而不是 __mutex};
}
1 //抢票的代码就可以更新成为  
2 #include<stdio.h>  
3 #include<stdlib.h>  
4 #include<string.h>  
5 #include<unistd.h>  
6 #include<pthread.h>  
7 #include"Lock.hpp"  
8  
9 using namespace LockModule;  
10  
11 int ticket = 1000;
#include <iostream>
#include <pthread.h>
#include <unistd.h> // 用于sleep函数Mutex mutex; // 假设Mutex是之前定义的互斥锁类// 票数
int ticket = 100; // 假设初始票数为100void* route(void* arg)
{char* id = (char*)arg;while (1){LockGuard lockguard(mutex); // 使用RAII风格的锁if (ticket > 0){sleep(1); // 休眠1秒printf("%s sells ticket: %d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}int main(void)
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void*)"thread 1");pthread_create(&t2, NULL, route, (void*)"thread 2");pthread_create(&t3, NULL, route, (void*)"thread 3");pthread_create(&t4, NULL, route, (void*)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}

在这里插入图片描述

RAII风格的互斥锁,C++11也有,比如:

std::_mutex mtx;
std::lock_guard<std::_mutex> guard(mtx);

后言

这就是线程的同步与互斥(一)。大家自己好好消化!今天就分享到这! 感谢各位的耐心垂阅!咱们下期见!拜拜~

http://www.dtcms.com/a/473836.html

相关文章:

  • 【开题答辩全过程】以 基于Android的小区物业管理APP的设计与实现为例,包含答辩的问题和答案
  • 【数据结构】二叉树-图解广度优先搜索
  • 临汾市建设局网站wordpress hacker主题
  • 【机器学习入门】7.1 决策树 —— 像 “判断流程图” 一样做分类
  • 虚拟机可以做两个网站物流网站有哪些
  • C++ 模拟题 力扣495. 提莫攻击 题解 每日一题
  • Google Chrome 开发者工具
  • 微信公众号平台开发文档深圳网站建设模板乐云seo
  • GitHub 热榜项目 - 日榜(2025-10-12)
  • 结构化特征生成推进广度学习:2025年深度学习领域的重要突破
  • 达梦数据库全库透明加密(TDE)解决方案:实现静态数据高安全防护
  • 深圳模板网站多少钱政务中心网站建设方案
  • spring boot拦截器获取requestBody的巨坑
  • [2]python爬虫实践,爬取网易云音乐热歌榜排行版名称
  • 网站快速备案公司wordpress文章末尾加上相关文章
  • WebAssembly联调实践:Rust计算模块与Node.js后端的性能对比
  • 利用万网做网站建筑工程网下载
  • 麒麟系统开机启动
  • Redis-List
  • PHP基础教程:从入门到精通
  • 瓦力机器人-舵机控制(基于树莓派5)
  • 建设银行南通通州支行网站如何改wordpress里的代码
  • linux网站环境网站公司的好坏
  • 音视频学习(六十九):视音频噪声
  • Python 爬虫实战 | Selenium 高效自动化:Headless + 无痕浏览深度解析
  • Java SpringMVC(三)--- SpringMVC,SpringIoCDI
  • 网站建设的验收网站上如何放入地图
  • Java 开发工具,最新2025 IDEA 使用,保姆级教程
  • 内网穿透~
  • 【Java EE进阶 --- SpringBoot】Mybatis操作数据库(基础二)