Linux:线程同步与互斥

目录

1.线程互斥

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

1.2互斥量mutex

1.3互斥量实现原理探究

2.线程同步

2.1条件变量

2.2同步概念与竞态条件

2.3条件变量函数

[2.4为什么 pthread_cond_wait 需要互斥量?](#2.4为什么 pthread_cond_wait 需要互斥量?)

2.5条件变量使用规范


1.线程互斥

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

临界资源:被多个线程共享访问的资源称为临界资源

临界区:线程中访问临界资源的那部分代码区域

互斥:确保同一时间仅有一个线程能进入临界区访问临界资源,从而实现对临界资源的保护

原子性:指不可被中断的完整操作,该操作只有完成或未完成两种状态

1.2互斥量mutex

  • 通常情况下,线程使用的数据都是局部变量,这些变量的地址空间位于线程栈内,属于单个线程独有,其他线程无法访问这些变量。

  • 但在某些情况下,需要在多个线程间共享变量,这类变量被称为共享变量。通过共享数据的机制,可以实现线程间的交互。

  • 当多个线程并发操作共享变量时,可能会引发一些问题。

操作共享变量会有问题的售票系统代码:

执行结果:

(注意gcc编译时需链接库文件-lpthread,否则pthread库函数使用不了)

此时我们可以注意到上述票出现了-1和-2的情况!!!!!!!!

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

• 互斥性:临界区的执行必须保持独占性,同一时间仅允许一个线程访问。

• 竞争处理:当多个线程同时请求进入空闲临界区时,系统需确保仅有一个线程获得执行权限。

• 非阻塞性:线程在非临界区执行时,不得妨碍其他线程正常进入临界区。

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

注意:

-- ticket 操作本⾝就不是⼀个原⼦操作

-- 该操作并非原子操作,实际由三条汇编指令组成:

  1. 加载(load) :将共享变量ticket从内存读取到寄存器中
  2. 更新(update):对寄存器中的值执行减1操作
  3. 存储(store) :将寄存器中的新值写回ticket的内存地址

(1)互斥量的接口

初始化互斥量

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

• 方法一:静态分配:

cpp 复制代码
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

• 方法2:动态分配

cpp 复制代码
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const 
pthread_mutexattr_t *restrict attr);

参数:mutex:要初始化的互斥量
attr:NULL

销毁互斥量

销毁互斥量需要注意:

• 使⽤ PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁

• 不要销毁⼀个已经加锁的互斥量

• 已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁

cpp 复制代码
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

cpp 复制代码
 int pthread_mutex_lock(pthread_mutex_t *mutex);

 int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值:成功返回0,失败返回错误号

调⽤ pthread_ lock 时,可能会遇到以下情况:

• 当互斥量处于未锁定状态时,该函数会成功锁定互斥量并返回成功状态

• 若调用函数时出现以下情况:

  • 其他线程已锁定该互斥量
  • 存在多个线程同时申请互斥量但未竞争成功 则pthread_lock调用将进入阻塞状态(执行流被挂起),直到互斥量被解锁

改进上面的售票系统:

执行结果:

发现现在售票系统变成正常!!!!!

1.3互斥量实现原理探究

• 通过上述示例可以看出,单纯的i++或++i操作都不是原子性的,可能引发数据一致性问题。

• 为实现互斥锁操作,多数体系结构提供了swap或exchange指令。该指令用于交换寄存器与内存单元的数据,由于是单条指令,能够保证原子性。即便在多处理器平台上,内存访问存在先后顺序,当某个处理器执行交换指令时,其他处理器的交换指令必须等待总线周期完成。现对lock和unlock的伪代码进行调整如下:

2.线程同步

2.1条件变量

  • 当一个线程以互斥方式访问某个变量时,有时会发现由于其他线程尚未改变状态,自己无法进行任何有效操作。

  • 例如,某线程访问队列时若发现队列为空,就必须等待,直到其他线程向队列中添加新元素。这种场景正是使用条件变量的典型情况。

注意:

条件变量通常需要配合互斥锁一起使用。

2.2同步概念与竞态条件

• 同步:在保障数据安全的基础上,通过协调线程访问临界资源的顺序来有效预防饥饿问题,这一机制称为同步。

• 竞态条件:指由于时序问题引发的程序异常现象。在多线程环境下,这种因执行顺序不确定性导致的问题尤为常见。

2.3条件变量函数

初始化

cpp 复制代码
 int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t 
*restrict attr);

参数:cond:要初始化的条件变量
attr:NULL

销毁

cpp 复制代码
int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

cpp 复制代码
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict 
mutex);

参数:cond:要在这个条件变量上等待
mutex:互斥量

唤醒等待

cpp 复制代码
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

简单案例:(来加强我们对条件变量的理解)

• 首先使用PTHREAD_COND/MUTEX_INITIALIZER进行初步测试,暂时忽略其他实现细节

执行结果:

线程按序打印的现象源于其默认在同一个条件变量下等待。每次唤醒操作会优先处理等待队列的首个线程,该线程完成打印任务后会重新进入队列尾部等待。这种机制形成了循环调度的效果。

2.4为什么 pthread_cond_wait 需要互斥量?

• 条件等待是线程间同步的关键机制。单独线程持续等待条件满足是无意义的,必须由其他线程修改共享变量来触发条件变化,并及时通知等待线程。

• 条件的满足必然涉及共享数据变更,因此必须通过互斥锁保护。缺乏互斥锁将无法安全地访问和修改共享数据。

• 按照上⾯的说法,我们设计出如下的代码:先上锁,发现条件不满⾜,解锁,然后等待在条件变 量上不就行了,如下代码:

• 解锁和等待操作必须保持原子性,否则可能出现线程永久阻塞的问题。具体而言,如果在解锁之后、pthread_cond_wait调用之前,其他线程获取了互斥量并发送了信号(此时条件已满足),pthread_cond_wait将无法接收到该信号,从而导致线程持续阻塞。因此,这两个操作必须作为一个不可分割的原子操作执行。

• int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 该函数执行时会首先检查条件变量是否为0:

  • 若条件量为0,则执行以下操作:
    1. 释放互斥锁
    2. 进入等待状态
  • 当函数返回时:
    1. 重新获取互斥锁
    2. 将条件量设置为1

2.5条件变量使用规范

•等待条件变量

cpp 复制代码
pthread_mutex_lock(&mutex);
while (条件为假)
        pthread_cond_wait(cond, mutex);

修改条件
pthread_mutex_unlock(&mutex);

•给条件发送信号代码

cpp 复制代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔2 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海3 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json