经过修改后的代码就可以确保线程 th1 的「加」操作会先于线程 th2 的「乘」操作:
- 情况一:线程 th1 先抢到锁,顺利执行「加」操作,并将线程 th2 的触发条件giFlag修改为 TRUE;继而当线程 th2 抢到锁后,不会进入到 while 循环 。
文章插图
- 情况二:线程 th2 先抢到锁,但由于此时giFlag为 FALSE , 所以会进入到 while 循环执行 pthread_cond_wait 语句,并阻塞在这儿释放掉 mutex;那么此时线程 th1 就可以顺利加锁,执行完「加」操作后将giFlag置为 TRUE,并发出信号,使得线程 th2 可以继续向下执行 。
文章插图
四、深入理解条件变量以下内容摘抄自 linux 下 pthread_cond_t 详解,博主写的很详细,通俗易懂
4.1 本文目的首先说明,本文重点不在怎么用条件变量 。这里我先列出 apue 中对于 pthread_cond_wait 函数的这么一段话:「调用者把锁住的互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁 。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化 。pthread_cond_wait 返回时,互斥量再次被锁住 。」
这段话的信息量很大,其中关于互斥量的操作可以理解为以下三个点:
- 调用 pthread_cond_wait 前需要先对互斥量 mutex 上锁,之后才能把 mutex 传入 pthread_cond_wait 函数
- 在 pthread_cond_wait 函数内部,会首先对传入的 mutex 解锁
- 当信号到来后,pthread_cond_wait 函数内部在返回前会去锁住传入的 mutex
4.2 三个问题要回答那三个问题,那么首先需要明白「等待与唤醒」的配合 。其实这个图就能解释上述三个问题 , 不过我还是详细解释一下 。
文章插图
图中有一个关键点,就是「判断条件是否满足」的操作,是在「调用 pthread_cond_wait 之前、锁 mutex 之后」发生的 。也就是说 pthread_cond_wait 不具备判断条件的能力,需要我们在外部写判断语句:
- 条件不满足时,才会进入 pthread_cond_wait
- 进入 pthread_cond_wait 先解锁然后就马上阻塞
- pthread_cond_signal 唤醒的是阻塞在 pthread_cond_wait 的线程
4.2.1 传入前为何要锁
传入前锁 mutex 是为了保证线程从条件判断到进入 pthread_cond_wait 前,条件不被改变 。如果没有传入前的锁,就会有这样的情况:线程 A 在「判断条件不满足之后、调用 pthread_cond_wait 之前」,A 因为休眠、又或者因为多线程下多个线程执行顺序和快慢的因素 , 令线程 B 更改了条件,使得条件满足 。但此时线程 A 还没有调用pthread_cond_wait 。等到线程 A 启动调用 pthread_cond_wait 后虽然条件满足,但却收不到 pthread_cond_signal 的唤醒,就会一直阻塞下去 。
结合下面的伪代码来加深理解:
/* 线程A执行函数 */int giFlag = FALSE; // FALSE:线程A不满足执行条件void *funcA(void *arg){ // pthread_mutex_lock(&mutex); // 传入前不加锁 while (FALSE == giFlag) { // 在调用pthread_cond_wait前,线程B启动并执行了函数funcB,修改条件并发出信号 // 但此时由于线程A还未执行pthread_cond_wait函数,所以会忽略掉线程B发出的信号 // 等到线程A开始执行pthread_cond_wait时,已经收不到来自线程B的信号了 , 会一直阻塞 pthread_cond_wait(&cond, &mutex); } // ToDo pthread_mutex_unlock(&mutex); return NULL;}/* 线程B执行条件 */void *funcB(void *arg){ pthread_mutex_lock(&mutex); // 线程B将giFlag置为TRUE,并通过cond_signal将信号发送给了线程A giFlag = TRUE; pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); return NULL;}
推荐阅读
- VLQ & Base64 VLQ 编码方式的原理及代码实现
- MYSQL-->InnoDB引擎底层原理
- StampedLock:一个并发编程中非常重要的票据锁
- Longchamp龙骧饺子包
- <三>从编译器角度理解C++代码编译和链接原理
- <一>关于进程虚拟地址空间区域内存划分和布局
- GitLab私有化部署 - CI/CD - 持续集成/交付/部署 - 源代码托管 & 自动化部署
- 🔥支持 Java 19 的轻量级应用开发框架,Solon v1.10.4 发布
- Bert不完全手册9. 长文本建模 BigBird & Longformer & Reformer & Performer
- XXI Open Cup, Grand Prix of Belarus 2020-2021 Winter Petrozavodsk Camp, Belarusian SU Contest 题解