pthread_cond_wait为什么要使用while循环判断?

要理解这个问题关键在于两点:

  • pthread_cond_signal/pthread_cond_broadcast只负责通知唤醒,并不能保证线程能够继续执行的条件。

  • pthread_cond_signal/pthread_cond_broadcast可能唤醒多个线程,多个线程之间会抢占竞争资源


下面具体分析:

当我们使用信号量进行来进行线程同步的时候,判断逻辑类似如下:

// 线程1
pthread_mutex_lock(&mtx);
while (某个条件下) {     // 这里为啥使用的是while不是if ?
    // 如果某个条件成立,线程阻塞在这里
    pthread_cond_wait(&cond,&mtx);
}
// 临界区进行某些操作
pthread_mutex_unlock(&mtx);

这里很多人有个疑问就是,为啥要使用while进行循环判断,而不是直接使用if判断

摘用深入理解pthread_cond_wait、pthread_cond_signal里面的一段话:

1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上 的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.
其实说白了很简单,就是pthread_cond_signal()也可能唤醒多个线程,而如果你同时只允许一个线程访问的话,就必须要使用while来进行条件判断,以保证临界区内只有一个线程在处理。

我觉得从本质来讲的话是,pthread_cond_signal函数只是负责唤醒线程(一个或多个),至于线程能够执行的时候(拿到锁),是否能够满足条件往下执行是不一定的

为什么线程受到唤醒信号之后,也不一定能够满足条件继续往下执行了呢?

  • pthread_cond_signal唤醒时,本来就不满足线程继续往下执行的条件,但是错误给唤醒了。(这种可能是编程逻辑有问题)

  • pthread_cond_signal唤醒时,线程满足继续执行条件,但是有多个线程,先执行的执行的线程由让条件变得不满足。

针对上面两个情况分别举个例子:

情况下1:pthread_cond_signal通知是并不满足继续执行条件

假如有个临界变量x,初始值为0。在线程1中,如果x <= 1,线程调用pthread_cond_wait函数阻塞。线程2中,将临界变量x设置为1,并调用pthread_cond_signal唤醒线程1。

线程1的判断如下:

pthread_mutex_lock(&mtx);
if (x <= 1 ) {
    pthread_cond_wait(&cond,&mtx);  // 线程阻塞在这个位置
}
// 临界区操作
pthread_mutex_unlock(&mtx);

虽然在线程2进行了唤醒,此时x = 1,但是按照我们的逻辑,此时线程1还是应该继续等待,但是如果使用if判断的话,从就第3行阻塞位置往下执行了,显然这不是我们希望的,如果使用while循环判断,就能避免这种条件不满足的情况。这说明pthread_cond_signal只负责通知唤醒,至于是否满足条件需要线程自己判断。


情况2:多个线程等竞争同一个变量,先执行的线程改变了条件

初始临界变量x = 0;

线程1:如果x< 1,阻塞。否则,继续执行,并设置x = 0;

线程2:如果x< 1,阻塞。否则,继续执行,并设置x = 0;

线程3:设置x = 1,调用pthread_cond_signal唤醒线程。

那么可能出现的情况是,pthread_cond_signal唤醒了两个线程,但是线程1先获得锁,线程执行后x = 0,然后线程2获得锁执行,按理说,此时线程2不应该继续执行,但是如果是if判断的话,就继续执行了。


具体的例子:

情况1:

#include 
#include 
#include
using namespace std;
static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
// 临界变量
static int x = 0;
void consumer() {
    pthread_mutex_lock(&mtx);
    // 如果x = 1,应该继续阻塞
    if (x <= 1) {
        pthread_cond_wait(&cond,&mtx);
    }
    cout << "x = " << x << endl;
    x--;
    pthread_mutex_unlock(&mtx);
}

void  producer() {
    pthread_mutex_lock(&mtx);
    x++;
    pthread_mutex_unlock(&mtx);
    pthread_cond_signal(&cond);
}

int main() {
    thread t1(consumer);
    thread t2(producer);
    t1.join();
    t2.join();
}

打印如下:

wake up... x = 1

可以看到在x = 1时,本来即使唤醒线程1,但是线程1还是应该继续等待,但是由于使用了if判断就继续执行了。但是使用while循环判断就可以避免这个问题。

情况2:

#include 
#include 
#include
using namespace std;
static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
// 临界变量
static int x = 0;
// 线程1
void consumer() {
    pthread_mutex_lock(&mtx);
    // 如果x = 1,应该继续阻塞
    if (x <= 1) {
        pthread_cond_wait(&cond,&mtx);
    }
    cout << "consumer wake up... x = " << x << endl;
    x--;
    pthread_mutex_unlock(&mtx);
}
// 线程2
void consumer2() {
    pthread_mutex_lock(&mtx);
    // 如果x = 1,应该继续阻塞
    if (x <= 1) {
        pthread_cond_wait(&cond,&mtx);
    }
    cout << "consumer2 wake up... x = " << x << endl;
    x--;
    pthread_mutex_unlock(&mtx);
}
// 线程3
void  producer() {
    pthread_mutex_lock(&mtx);
    x = 2;
    pthread_mutex_unlock(&mtx);
    pthread_cond_broadcast(&cond);
}

int main() {
    thread t1(consumer);
    thread t2(consumer2);
    thread t3(producer);
    t1.join();
    t2.join();
    t3.join();
}

打印如下:

consumer wake up... x = 2
consumer2 wake up... x = 1

线程3中将x设置为2,通知唤醒了线程1和线程2。线程1先拿到锁,执行完之后x = 1,然后线程2拿到锁,此时x= 1,应该继续等待的,但是由于是if判断,就继续往下执行了。


综上所述,pthread_cond_wait使用while循环判断,可以避免收到错误的唤醒信号或 多线程竞争时,线程本应该继续阻塞等待但是却错误的往下执行了。

你可能感兴趣的:(操作系统,多线程,C++多线程,while循环判断)