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);
相关推荐
一 乐2 小时前
旅游|内蒙古景点旅游|基于Springboot+Vue的内蒙古景点旅游管理系统设计与实现(源码+数据库+文档)
开发语言·前端·数据库·vue.js·spring boot·后端·旅游
YDS8292 小时前
苍穹外卖 —— Spring Cache和购物车功能开发
java·spring boot·后端·spring·mybatis
苍老流年2 小时前
1. SpringBoot初始化器ApplicationContextInitializer使用与源码分析
java·spring boot·后端
劲墨难解苍生苦2 小时前
spring ai alibaba mcp 开发demo
java·人工智能
leonardee2 小时前
Spring 中的 @ExceptionHandler 注解详解与应用
java·后端
不爱编程的小九九2 小时前
小九源码-springboot103-踏雪阁民宿订购平台
java·开发语言·spring boot
共享家95272 小时前
LRU 缓存的设计与实现
开发语言·c++
Elieal2 小时前
Spring 框架核心技术全解析
java·spring·sqlserver
组合缺一2 小时前
(对标 Spring)OpenSolon v3.7.0, v3.6.4, v3.5.8, v3.4.8 发布(支持 LTS)
java·后端·spring·web·solon