目录
[20.1 什么是线程互斥?](#20.1 什么是线程互斥?)
[20.2 为什么需要线程互斥?](#20.2 为什么需要线程互斥?)
[20.3 互斥锁mutex](#20.3 互斥锁mutex)
[20.4 互斥量的接口](#20.4 互斥量的接口)
[20.4.1 互斥量初始](#20.4.1 互斥量初始)
[20.4.2 互斥量销毁](#20.4.2 互斥量销毁)
[20.4.3 互斥量加锁](#20.4.3 互斥量加锁)
[20.4.4 互斥量解锁](#20.4.4 互斥量解锁)
[20.4.5 互斥量的基本原理](#20.4.5 互斥量的基本原理)
[20.4.6 带上互斥锁后的抢票程序](#20.4.6 带上互斥锁后的抢票程序)
[20.5 死锁问题](#20.5 死锁问题)
[20.6 互斥量的实现机制](#20.6 互斥量的实现机制)
[21.1 同步概念与竞态条件](#21.1 同步概念与竞态条件)
[21.2 条件变量](#21.2 条件变量)
[21.2.1 条件变量初始](#21.2.1 条件变量初始)
[21.2.2 条件变量销毁](#21.2.2 条件变量销毁)
[21.2.3 等待满足](#21.2.3 等待满足)
[21.2.3 唤醒等待](#21.2.3 唤醒等待)
[21.3 利用条件变量实现线程同步](#21.3 利用条件变量实现线程同步)
[21.4 为什么pthread_cond_wait需要互斥量?](#21.4 为什么pthread_cond_wait需要互斥量?)
[21.5 条件变量使用规范](#21.5 条件变量使用规范)
二十.线程互斥
20.1 什么是线程互斥?
线程互斥是一种同步机制,用于控制对共享资源的访问,以确保在任意给定的时刻只有一个线程可以访问该资源。在多线程编程中,当多个线程同时竞争访问某个共享资源时,如果没有适当的同步机制,可能会导致竞争条件和数据不一致性的问题。线程互斥通过引入互斥锁等机制,使得在任意时刻只能有一个线程持有资源的访问权限,从而避免了竞争条件和数据不一致性的发生。
进程线程间的互斥相关背景概念
- 临界资源: 多线程执行流共享的资源叫做临界资源。
- 临界区: 每个线程内部,访问临界资源的代码,就叫做临界区。
- **互斥:**任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
- 原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。
20.2 为什么需要线程互斥?
这里我们直接举个栗子来回答这个问题,我们用代码来模拟一个抢票机制,这里的所定义的票数tickets就是所谓的临界资源,这里我们一共创建5个线程来模拟抢票程序,并不断打印时时监测抢票过程
cpp
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
int tickets = 1000;//定义一个全局变量,这就是临界资源,1000张票
void* ThreadRotinue(void* args)
{
int id = *(int*)args;
delete (int*)args;
while(true)
{
if(tickets > 0)
{
usleep(10000); //usleep函数能把线程挂起一段时间, 单位是微秒(千分之一毫秒)。
printf("线程[%d] 抢票:%d\n", id, tickets);
tickets--; //抢票,票数递减
}
else
{
break;
}
}
}
int main()
{
pthread_t tid[5];
for(int i = 0; i < 5; i++)//主线程创建出5个线程去抢票
{
int* id = new int(i);
pthread_create(tid + i, nullptr, ThreadRotinue, id);
}
for(int i = 0; i < 5; i++)
{
pthread_join(tid[i], nullptr); //等待线程
}
return 0;
}
运行结果如下:
这里我们惊讶的发现,结果竟然出现了票数剩余为负数的情况!
该代码中记录剩余票数的变量tickets就是临界资源,因为它被多个执行流同时访问,而判断tickets是否大于0、打印剩余票数以及--tickets
这些代码就是临界区,因为这些代码对临界资源进行了访问。
分析剩余票数出现负数的原因:
- if语句判断条件为真以后,代码可以并发的切换到其他线程。
- usleep用于模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段。
--
**ticket
**操作本身就不是一个原子操作。
为什么--ticket
不是原子操作?
我们对一个变量进行--
,我们实际需要进行以下三个步骤:
Load(加载) :将共享变量
tickets
从内存加载到寄存器中。这一步确保了线程正在使用的是最新的变量值。Update(更新):在寄存器中执行减 1 操作。这意味着对寄存器中的值进行修改,而不是直接在内存中进行修改,以确保线程独占了这个操作。
Store(存储) :将新的值从寄存器写回到共享变量
tickets
的内存地址。这样可以确保其他线程在需要访问该变量时,能够获取到更新后的值。
--
操作对应的汇编代码如下 :
这个过程我们可以用下面几个图片形象表示 :
1.现在有两个线程thread1和thread2,thread1处于运行中、thread2等待中
当thread1把tickets的值读进CPU由于时间片耗尽被切走了,假设此时thread1读取到的值就是1000,而当thread1被切走时,寄存器中的1000叫做thread1的上下文信息,因此需要被保存起来,之后thread1就被挂起了,放到了等待队列。(也就是说thread1只进行了Load(加载)操作)
2.此时thread2被调度了,thread1处于等待中
此时thread2被调度了,由于thread1只进行了 Load(加载),此时thread2此时看到tickets的值还是1000,有可能系统给thread2的时间片较多,导致thread2一次性执行了100次完整的 --操作才被切走,最终tickets由1000减到了900。
3.thread2时间片耗尽被切走了,切到thread1带着上下文信息恢复
此时thread2时间片到了被挂起了,又切换到了thread1,它就带着上下文过来恢复,而他的上下文记录到它还处于刚刚完成对ticket的Load(加载)操作 ,此时寄存器中load的ticket值仍然是1000,这时它接着完成了Update(更新)操作 ,也就是对1000减到999,最后再然后再Store(存储)操作 将更新的ticket写回到内存中,此时内存中的值又由900变成了999
为了解决这个问题,这里我们引入互斥锁mutex的概念
20.3 互斥锁mutex
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况变量归属单个线程,其他线程无法获得这种变量。
- 但有时候,很多变量都需要在线程间共享,这样的变量成为共享变量,可以通过数据的共享,完成线程之间的交互。
- 多个线程并发的操作共享变量,就会带来一些问题。
要解决上述抢票系统的问题,需要做到三点:
- 代码必须有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
- 如果多个线程同时要求执行临界区的代码,并且此时临界区没有线程在执行,那么只能允许一个线程进入该临界区。
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
20.4互斥量的接口
20.4.1 互斥量初始
在使用互斥量之前,需要对其进行初始化。一般使用 pthread_mutex_init 函数进行初始化,其原型如下:
cpp
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数说明:
- mutex:需要初始化的互斥量。
- attr:初始化互斥量的属性,一般设置为NULL即可。
返回值说明:
- 互斥量初始化成功返回0,失败返回错误码。
调用pthread_mutex_init函数初始化互斥量叫做动态分配 ,除此之外,我们还可以用下面这种方式初始化互斥量,该方式叫做静态分配:
cpp
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
20.4.2 互斥量销毁
在不再需要使用互斥量时,需要将其销毁以释放资源。一般使用 pthread_mutex_destroy 函数进行销毁,其原型如下:
cpp
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数说明:
- mutex:需要销毁的互斥量。
返回值说明:
- 互斥量销毁成功返回0,失败返回错误码。
销毁互斥量需要注意:
- 使用
PTHREAD_MUTEX_INITIALIZER
初始化(静态分配)的互斥量不需要销毁。 - 不要销毁一个已经加锁的互斥量。
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。
20.4.3 互斥量加锁
当线程需要访问临界资源时,需要先对互斥量加锁,以确保只有一个线程能够进入临界区。一般使用 pthread_mutex_lock 函数进行加锁,其原型如下:
cpp
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数说明:
- mutex:要加锁的互斥量
返回值说明:
- 加锁成功返回0,失败返回错误码。
调用pthread_mutex_lock
时,可能会遇到以下情况:
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么
pthread_mutex_lock
调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
20.4.4 互斥量解锁
当线程访问完临界资源后,需要对互斥量解锁,以允许其他线程进入临界区。一般使用 pthread_mutex_unlock
函数进行解锁,其原型如下:
cpp
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数说明:
- mutex:需要解锁的互斥量。
返回值说明:
- 互斥量解锁成功返回0,失败返回错误码。
20.4.5 互斥量的基本原理
互斥量的初始化与销毁: 在使用互斥量之前,需要对其进行初始化,一般通过
pthread_mutex_init
函数实现。销毁互斥量时使用pthread_mutex_destroy
函数。这些操作确保互斥量的正确性和可用性。互斥量的加锁与解锁: 当线程需要访问临界资源时,首先需要对互斥量进行加锁,以确保只有一个线程能够进入临界区。加锁使用
pthread_mutex_lock
函数,解锁则使用pthread_mutex_unlock
函数。这些操作保证了临界资源的独占性。
引入互斥量后,当一个线程申请到锁进入临界区时,在其他线程看来该线程只有两种状态,要么没有申请锁,要么锁已经释放了,因为只有这两种状态对其他线程才是有意义的。
例如,图中线程1进入临界区后,在线程2、3、4看来,线程1要么没有申请锁,要么线程1已经将锁释放了,因为只有这两种状态对线程2、3、4才是有意义的,当线程2、3、4检测到其他状态时也就被阻塞了
此时对于线程2、3、4而言,它们就认为线程1的整个操作过程是原子的。
临界区内的线程可能进行线程切换吗?
临界区内的线程完全可能进行线程切换,但即便该线程被切走,其他线程也无法进入临界区进行资源访问,因为此时该线程是拿着锁被切走的,锁没有被释放也就意味着其他线程无法申请到锁,也就无法进入临界区进行资源访问了。
其他想进入该临界区进行资源访问的线程,必须等该线程执行完临界区的代码并释放锁之后,才能申请锁,申请到锁之后才能进入临界区。
20.4.6 带上互斥锁后的抢票程序
cpp
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
int tickets = 1000; // 定义一个全局变量,这就是临界资源,1000张票
pthread_mutex_t mutex; // 定义互斥锁
void* ThreadRotinue(void* args)
{
int id = *(int*)args;
delete (int*)args;
while(true)
{
pthread_mutex_lock(&mutex); // 加锁
if(tickets > 0)
{
usleep(10000); // usleep函数能把线程挂起一段时间,单位是微秒(千分之一毫秒)。
printf("线程[%d] 抢票:%d\n", id, tickets);
tickets--; // 抢票,票数递减
}
else
{
pthread_mutex_unlock(&mutex); // 解锁
break;
}
pthread_mutex_unlock(&mutex); // 解锁
}
}
int main()
{
pthread_t tid[5];
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
for(int i = 0; i < 5; i++) // 主线程创建出5个线程去抢票
{
int* id = new int(i);
pthread_create(tid + i, nullptr, ThreadRotinue, id);
}
for(int i = 0; i < 5; i++)
{
pthread_join(tid[i], nullptr); // 等待线程
}
pthread_mutex_destroy(&mutex); // 销毁互斥锁
return 0;
}
演示效果:
20.5 死锁问题
死锁是多线程编程中常见的问题,指的是两个或多个线程相互等待对方持有的资源,导致所有线程都无法继续执行的状态。 在使用互斥锁时,如果不注意锁的加锁顺序,就容易导致死锁问题。
这里我们举一个经典的死锁例子:
cpp
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mutex1;
mutex mutex2;
void threadFunction1() {
lock_guard<mutex> lock1(mutex1);
this_thread::sleep_for(chrono::milliseconds(100));
lock_guard<mutex> lock2(mutex2);
cout << "Thread 1 acquired mutex1 and mutex2" << endl;
}
void threadFunction2() {
lock_guard<mutex> lock2(mutex2);
this_thread::sleep_for(chrono::milliseconds(100));
lock_guard<mutex> lock1(mutex1);
cout << "Thread 2 acquired mutex2 and mutex1" << endl;
}
int main() {
thread t1(threadFunction1);
thread t2(threadFunction2);
t1.join();
t2.join();
return 0;
}
运行代码后:
可以观察到,此时程序就处于一个被阻塞的状态
用ps命令查看该进程时可以看到,该进程当前的状态是Sl+,其中的l实际上就是lock的意思,表示该进程当前处于一种死锁的状态。
- 在这个示例中,有两个线程,每个线程都试图先锁定 mutex1,然后再锁定 mutex2。当一个线程已经锁定了 mutex1,而另一个线程已经锁定了 mutex2,那么它们都会等待对方释放对方所持有的互斥量。这种情况下就可能发生死锁。
- 例如,线程1获得了 mutex1 的锁,然后暂停,线程2获得了 mutex2 的锁,然后暂停。接下来,线程1试图获取 mutex2 的锁,但由于线程2已经持有了 mutex2 的锁,因此线程1会被阻塞。同样的,线程2也试图获取 mutex1 的锁,但由于线程1已经持有了 mutex1 的锁,因此线程2也会被阻塞。这样,两个线程就会相互等待,导致死锁。
- 为了避免这种死锁,我们应该保持一致的锁定顺序。例如,可以约定所有线程都先锁定 mutex1,然后再锁定 mutex2。
死锁的四个必要条件
- 互斥条件: 一个资源每次只能被一个执行流使用。
- 请求与保持条件: 一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系。
注意: 这是死锁的四个必要条件,也就是说只有同时满足了这四个条件才可能产生死锁。
如何避免死锁
- 加锁顺序:对多个互斥量加锁时,保持一致的加锁顺序,避免不同线程以不同的顺序加锁而导致死锁。
- 加锁时间:尽量减小临界区的范围,在持有锁的时间内,尽快完成对资源的操作。
- 超时机制:在获取锁的时候设置超时,如果超过一定时间仍未获得锁,则放弃获取资源。
- 避免嵌套锁:尽量避免在一个互斥区域内再次申请其他锁,以免造成死锁。
除此之外,还有一些避免死锁的算法,比如死锁检测算法和银行家算法。
20.6互斥量的实现机制
锁是否需要被保护?
我们说被多个执行流共享的资源叫做临界资源,访问临界资源的代码叫做临界区。所有的线程在进入临界区之前都必须竞争式的申请锁,因此锁也是被多个执行流共享的资源,也就是说锁本身就是临界资源。
既然锁是临界资源,那么锁就必须被保护起来,但锁本身就是用来保护临界资源的,那锁又由谁来保护的呢?
锁实际上是自己保护自己的,我们只需要保证申请锁的过程是原子的,那么锁就是安全的。
如何实现申请锁的过程是原子的?
- 上面我们已经说明了
--
和++
操作不是原子操作,可能会导致数据不一致问题。 - 要保证申请锁的过程是原子的,通常使用底层的硬件指令来实现。大多数体系结构提供了一种原子交换指令,如 xchg 或 exchange 指令。这些指令可以在一个操作中完成寄存器和内存单元之间的数据交换,保证了这个操作的原子性。因此,申请锁的过程可以通过这些原子交换指令来实现,确保在任何时候只有一个线程能够成功地获取锁。
下面我们来看看lock和unlock的伪代码:
%al是一个cpu上的寄存器,xchgb是交换指令。
我们创建锁,本质是在内存上创建一个变量,初始化锁是将锁的初始化为一个非0的值。
例如,此时内存中mutex的值为1,thread1申请锁时先将al寄存器中的值设为0,然后将al寄存器中的值与内存中mutex的值进行交换。
交换完成后检测该线程的al寄存器中的值为1,则该线程申请锁成功,可以进入临界区对临界资源进行访问。
而此后的thread2若是再申请锁,与内存中的mutex交换得到的值就是0了,此时该线程申请锁失败,需要被挂起等待,直到锁被释放后再次竞争申请锁
二十一.线程同步
21.1 同步概念与竞态条件
- **同步概念:**同步是指在多线程环境下,协调不同线程之间的执行顺序和操作,以确保它们能够按照预期的顺序执行和相互协作。在多线程编程中,同步用于解决竞争条件和数据一致性的问题,确保线程之间的协作正确可靠。
- 竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件。
举个生活例子:
同步就是操作过程中必须要有先后,比如妈妈做完饭后,儿子才能开始吃饭。一家人到齐后才能吃饭。
- 首先需要明确的是,单纯的加锁是会存在某些问题的,如果个别线程的竞争力特别强,每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,这就可能导致其他线程长时间竞争不到锁,引起饥饿问题。
- 单纯的加锁是没有错的,它能够保证在同一时间只有一个线程进入临界区,但它没有高效的让每一个线程使用这份临界资源。
- 现在我们增加一个规则,当一个线程释放锁后,这个线程不能立马再次申请锁,该线程必须排到这个锁的资源等待队列的最后。
- 增加这个规则之后,下一个获取到锁的资源的线程就一定是在资源等待队列首部的线程,如果有十个线程,此时我们就能够让这十个线程按照某种次序进行临界资源的访问。
21.2 条件变量
条件变量是一种线程同步机制,用于在多个线程之间进行协调和通信。条件变量通常与互斥锁结合使用,用于等待某个条件的发生。当条件不满足时,线程可以调用条件变量的等待操作来等待条件的发生,同时释放互斥锁,让其他线程能够进入临界区。当条件满足时,线程可以调用条件变量的通知操作来通知等待的线程条件已经满足,从而唤醒等待的线程继续执行。
21.2.1 条件变量初始
初始化分为两种:
cpp
//动态分配
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//静态分配
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
参数说明:
- **cond:**需要初始化的条件变量。
- **attr:**初始化条件变量的属性,一般设置为NULL。
返回值说明:
- 成功返回0,失败返回错误码。
21.2.2 条件变量销毁
条件变量的销毁可以使用 pthread_cond_destroy 函数,用于释放条件变量占用的资源。
cpp
int pthread_cond_destroy(pthread_cond_t *cond);
参数说明:
- **cond:**需要销毁的条件变量。
返回值说明:
- 成功返回0,失败返回错误码。
使用静态分配初始化的条件变量不需要销毁;
21.2.3 等待满足
线程可以使用条件变量的等待操作来等待条件的发生。等待操作通常与互斥锁一起使用,以确保等待操作的原子性。
cpp
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数说明:
- **cond:**需要等待的条件变量。
- **mutex:**当前线程所处临界区对应的互斥锁。
返回值说明:
- 成功返回0,失败返回错误码。
21.2.3 唤醒等待
条件变量的通知操作用于唤醒等待条件的线程。有两种通知方式:
唤醒单个等待线程 :使用 pthread_cond_signal
函数。
cpp
int pthread_cond_signal(pthread_cond_t *cond);
唤醒全部等待线程 :使用 pthread_cond_broadcast
函数。
cpp
int pthread_cond_broadcast(pthread_cond_t *cond);
参数说明:
- **cond:**唤醒在cond条件变量下等待的线程。
返回值说明:
- 函数调用成功返回0,失败返回错误码。
21.3 利用条件变量实现线程同步
cpp
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
#define NUM 5
pthread_mutex_t mtx;
pthread_cond_t cond;
int tickets = 100;
void* producer(void* args)
{
string name = (char*)args;
while (tickets > 0)
{
pthread_mutex_lock(&mtx); // 加锁
// 生产者线程在唤醒消费者线程之前修改共享资源
pthread_cond_signal(&cond);//1.唤醒在条件变量下一个线程
pthread_mutex_unlock(&mtx); // 解锁
sleep(1);
}
}
void* buyer(void* args)
{
int id = *(int*)args;
delete (int*)args;
pthread_mutex_lock(&mtx); // 加锁
while (tickets > 0) {
// 消费者线程在循环中等待条件变量的信号
pthread_cond_wait(&cond, &mtx); // 等待唤醒
if (tickets > 0) {
cout << "线程[" << id << "] 抢到票:" << tickets << endl;
tickets--; // 抢票,票数递减
}
}
pthread_mutex_unlock(&mtx); // 解锁
return NULL;
}
int main()
{
pthread_mutex_init(&mtx, nullptr);
pthread_cond_init(&cond, nullptr);
pthread_t master; // 创建生产者线程
pthread_t worker[NUM]; // 创建消费者线程数组
pthread_create(&master, nullptr, producer, (void*)"boss");
for(int i = 0; i < NUM; i++)
{
int* num = new int(i);
pthread_create(worker + i, nullptr, buyer, (void*)num);
}
for(int i = 0; i < NUM; i++)
{
pthread_join(worker[i], nullptr);
}
pthread_join(master, nullptr);
pthread_mutex_destroy(&mtx);
pthread_cond_destroy(&cond);
return 0;
}
此时我们会发现这五个线程时具有明显的顺序性,这是因为这5个线程启动时默认都会在该条件变量下去等待,而我们每次都唤醒的是在当前条件变量下等待的第一个线程,当该线程执行完打印操作后会继续排到等待队列的尾部进行等待,所以我们能够看到一个轮换的现象。 这样就实现了线程的同步
21.4 为什么pthread_cond_wait需要互斥量?
- 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
- 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化,所以一定要用互斥锁来保护,没有互斥锁就无法安全的获取和修改共享数据。
- 当线程进入临界区时需要先加锁,然后判断内部资源的情况,若不满足当前线程的执行条件,则需要在该条件变量下进行等待,但此时该线程是拿着锁被挂起的,也就意味着这个锁再也不会被释放了,此时就会发生死锁问题。
- 所以在调用
pthread_cond_wait
函数时,还需要将对应的互斥锁传入,此时当线程因为某些条件不满足需要在该条件变量下进行等待时,就会自动释放该互斥锁。 - 当该线程被唤醒时,该线程会接着执行临界区内的代码,此时便要求该线程必须立马获得对应的互斥锁,因此当某一个线程被唤醒时,实际会自动获得对应的互斥锁。
按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码:
cpp
//错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false){
pthread_mutex_unlock(&mutex);
//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
调用解锁之后,在调用pthread_cond_wait函数之前,如果已经有其他线程获取到互斥量,发现此时条件满足,于是发送了信号,那么此时pthread_cond_wait函数将错过这个信号,最终可能会导致线程永远不会被唤醒
21.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);