【多线程】五、线程同步 条件变量
文章目录
- Ⅰ. 线程同步概念
- Ⅱ. 条件变量
- 一、条件变量的概念
- 二、条件变量的定义
- 三、条件变量的使用
- ① pthread_cond_init() 初始化
- ② pthread_cond_destroy() 销毁
- ③ 等待条件满足
- ④ 唤醒等待
- ⑤ 代码测试
- 四、为什么 pthread_cond_init() 需要互斥量❓

Ⅰ. 线程同步概念
还记得我们在学线程互斥的时候写的抢票系统代码吗,虽说我们通过使用互斥量来防止多线程访问时候的不安全问题,但是还有一个问题我们还没提及,就是如果当我们执行完临界区代码的时候,如果不进行 usleep()
的话,会发现执行结果全是单个线程在抢着票,而这就涉及到了线程同步与竞争条件问题!下面我们一一来了解一下它们!
- 线程同步:在保证数据安全的前提下,让多个线程能够按照一定的顺序访问临界资源,从而有效 避免饥饿问题,叫做线程同步。
- 竞态条件:因为时序问题,当多个线程访问同一个共享变量时,最终的结果取决于线程执行的顺序,导致结果不可预测。
为了解决这些问题,可以使用同步机制来协调线程之间的访问。常用的同步机制有:
- 互斥锁(
Mutex
):在进入临界区前获取锁,执行完临界区代码后释放锁,保证同一时间只有一个线程可以访问临界区。 - 屏障(
Barrier
):多个线程需要等待其他线程执行完特定代码后再继续执行,可以使用屏障来同步线程的执行。 - 信号量(
Semaphore
):控制对共享资源的访问数量,当信号量为0
时,线程需要等待;当信号量为1
时,线程可以访问共享资源。 - 条件变量(
Condition Variable)
:当某个条件不满足时,线程进入等待状态,直到其他线程发出特定信号或事件满足条件后,才会唤醒等待线程。
之前我们通过了互斥锁和屏障(解锁完休眠了一会)在一定程度上解决了这个问题,下面我们来学习条件变量和信号量!
Ⅱ. 条件变量
一、条件变量的概念
条件变量(Condition Variable
)是一种同步机制,通常与互斥锁(Mutex
)结合使用,用于线程间的等待和唤醒。它提供了一个让线程进入等待状态并释放锁的机制:
- 当某个条件满足时,唤醒等待线程继续执行。
- 当某个条件不满足时,线程进入等待状态,直到其他线程发出特定信号或事件满足条件后,才会唤醒等待线程。
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了,一直阻塞着。例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
条件变量可以分为两种类型:一种是用于通知单个线程的条件变量(signal
),另一种是用于通知多个线程的条件变量(broadcast
)。使用条件变量的基本流程如下:
- 初始化:使用
pthread_cond_init()
函数初始化条件变量。 - 等待:使用
pthread_cond_wait()
函数让线程进入等待状态并释放互斥锁,直到其他线程调用条件变量的通知函数将其唤醒。 - 通知单个线程:使用
pthread_cond_signal()
函数唤醒等待队列中的一个线程,通常是等待时间最长的线程。 - 通知多个线程:使用
pthread_cond_broadcast()
函数唤醒等待队列中的所有线程。 - 销毁:使用
pthread_cond_destroy()
函数销毁条件变量。
接下来我们先学学条件变量的这些接口,然后通过生产者消费者模型来深入理解条件变量的用处!
二、条件变量的定义
定义条件变量对象的代码如下:
pthread_cond_t condition;
pthread_cond_t
数据类型声明如下:
// come from /usr/include/bits/pthreadtypes.h
/* Data structure for conditional variable handling. The structure of the attribute type is not exposed on purpose. */
typedef union
{struct{int __lock; // 用于实现对条件变量的互斥访问unsigned int __futex; // 一个底层的系统调用,用于实现线程等待和唤醒,它是一个用户空间和内核空间之间的交互接口,用于实现线程的同步和通信__extension__ unsigned long long int __total_seq; // 记录条件变量被唤醒的次数,包括所有线程的唤醒次数__extension__ unsigned long long int __wakeup_seq; // 记录条件变量被唤醒的次数__extension__ unsigned long long int __woken_seq; // 最后一次被唤醒的时间戳void *__mutex; // 与条件变量相关联的互斥锁的指针/地址unsigned int __nwaiters; // 记录当前等待的线程数unsigned int __broadcast_seq; // 记录最后一次broadcast的时间戳} __data;char __size[__SIZEOF_PTHREAD_COND_T]; // 指定条件变量的大小__extension__ long long int __align; // 指定该类型的对齐方式
} pthread_cond_t;
三、条件变量的使用
① pthread_cond_init() 初始化
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);// 功能:动态初始化条件变量
// 返回值:成功返回0,失败返回错误编号
// 参数:
// cond:线程要等待的目标条件变量
// attr:条件变量属性,默认设为nullptr即可
上面那种方法是通过动态初始化条件变量,其实我们还可以通过静态的方式来初始化条件变量,并且这种方式不用进行条件变量的销毁!
#include <pthread.h>
pthread_cond_t cond