【线程系列之三】条件变量介绍

一、基础概念

条件变量(Condition Variables)是并发编程中用于线程同步的一种机制,它通常与互斥锁(Mutex)一起使用,以允许线程以某种条件为基础来挂起(阻塞)和唤醒(继续执行)。条件变量提供了两种基本操作:等待(wait)和通知(notify/notifyAll)。

二、注意事项

  • 条件变量必须和互斥锁一起使用,以避免竞态条件。

  • 在调用pthread_cond_wait之前,线程必须持有与条件变量关联的互斥锁。

  • 调用pthread_cond_wait时,线程会释放互斥锁并进入等待状态,直到被唤醒并重新获得互斥锁。

  • 在调用pthread_cond_signalpthread_cond_broadcast之前,线程也必须持有与条件变量关联的互斥锁。

  • 使用条件变量时,通常使用while循环来检查条件是否满足,而不是if语句,以避免虚假唤醒。

三、常用函数【POSIX线程(pthread)库】

3.1 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

参数说明:

  • cond: 指向条件变量的指针。

  • attr: 指向条件变量属性的指针,通常设置为NULL,表示使用默认属性。

  • 返回值: 成功返回0,失败返回错误码。

3.2 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

参数说明:

  • cond: 指向条件变量的指针。

  • 返回值: 成功返回0,失败返回错误码。

3.3 pthread_cond_wait
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

将当前线程挂起,直到有信号将其唤醒或出现错误返回。在等待期间,会释放与条件变量关联的互斥锁,当线程被唤醒后重新获取互斥锁。

参数说明:

  • cond: 指向条件变量的指针。

  • mutex: 指向互斥锁的指针。

  • 返回值: 成功返回0,失败返回错误码。

3.4 pthread_cond_timedwait
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

类似于pthread_cond_wait,但加入了超时限制。如果在超时时间之前条件没有满足,函数将返回一个错误码。

参数说明:

  • cond: 指向条件变量的指针。

  • mutex: 指向互斥锁的指针。

  • abstime:指向timespec结构体的指针,表示等待的绝对时间。

  • 返回值: 成功返回0,超时返回ETIMEDOUT,其他失败返回错误码。

3.5 pthread_cond_signal
int pthread_cond_signal(pthread_cond_t *cond);

唤醒等待给定条件变量的一个线程。如果有多个线程在等待,则唤醒其中一个线程。

参数说明:

  • cond: 指向条件变量的指针。

  • 返回值: 成功返回0,失败返回错误码。

3.6 pthread_cond_broadcast
int pthread_cond_broadcast(pthread_cond_t *cond);

唤醒所有等待给定条件变量的线程。

参数说明:

  • cond: 指向条件变量的指针。

  • 返回值: 成功返回0,失败返回错误码。

四、代码示例

4.1 示例1

以下是一个简单的示例,展示了如何使用条件变量和互斥锁来实现生产者-消费者模型。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer = 0; // 假设这是一个简单的缓冲区,用于存储整数

void* producer(void* arg) {
    for (int i = 0; i < 10; ++i) {
        pthread_mutex_lock(&mutex);
        // 生产者生产产品,这里简单地将buffer加1
        buffer++;
        printf("Producer produced %d\n", buffer);

        // 唤醒可能在等待的消费者
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);

        sleep(1); // 模拟生产耗时
    }
    return NULL;
}

void* consumer(void* arg) {
    while (1) {
        pthread_mutex_lock(&mutex);

        // 等待直到有产品可消费
        while (buffer == 0) {
            pthread_cond_wait(&cond, &mutex);
        }

        // 消费产品,这里简单地将buffer减1
        buffer--;
        printf("Consumer consumed %d\n", buffer);

        pthread_mutex_unlock(&mutex);

        sleep(2); // 模拟消费耗时
    }
    return NULL;
}

int main() {
    pthread_t tid_producer, tid_consumer;

    // 创建生产者和消费者线程
    pthread_create(&tid_producer, NULL, producer, NULL);
    pthread_create(&tid_consumer, NULL, consumer, NULL);

    // 等待线程结束
    pthread_join(tid_producer, NULL);
    pthread_join(tid_consumer, NULL);

    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

运行结果为:
在这里插入图片描述

在上述的生产-消费者模型中存在一个问题,及buffer最终会变为0,生产者线程在生产了10个产品后就结束了,而消费者线程则在一个无限循环中运行,不断尝试消费产品。当所有的产品都被消费掉(即buffer变为0),且生产者不再生产新的产品时,消费者线程将在pthread_cond_wait(&cond, &mutex);处无限期地等待,因为没有其他线程会再次调用pthread_cond_signal(&cond);pthread_cond_broadcast(&cond);来唤醒它。

则,我们可以采用以下三类方法解决上述问题:

  • 使用标志位来控制线程的执行:在共享数据中添加一个标志位(如int done;),当生产者完成生产后,将其设置为1。消费者线程在检查buffer是否为0之前,也会检查这个标志位。如果done1buffer0,则消费者可以安全地退出循环并结束线程。

  • 让消费者线程定期检查终止条件:消费者线程可以在每次消费产品后检查一个终止条件(如一个全局变量或共享数据结构中的某个字段)。如果满足终止条件,则退出循环并结束线程。

  • 使用其他同步机制:使用另一个条件变量来专门通知消费者线程何时可以安全地退出。

4.2 使用标志位来控制线程执行
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer = 0;
int done = 0; // 新增的标志位

void* producer(void* arg) {
    for (int i = 0; i < 10; ++i) {
        pthread_mutex_lock(&mutex);
        buffer++;
        printf("i = %d, Producer produced %d\n", i, buffer);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    pthread_mutex_lock(&mutex);
    done = 1; // 标记生产完成
    pthread_cond_signal(&cond); // 可选:唤醒可能还在等待的消费者
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* consumer(void* arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
        while (buffer == 0 && !done) { // 检查done标志位
            pthread_cond_wait(&cond, &mutex);
        }
        if (done && buffer == 0) { // 如果done为true且buffer为0,则退出循环
            break;
        }
        buffer--;
        printf("Consumer consumed %d\n", buffer);
        pthread_mutex_unlock(&mutex);
        sleep(2);
    }
    return NULL;
}

// ... main函数和其他部分保持不变 ...

运行结果为:

在这里插入图片描述

在这个修改后的示例中,生产者线程在生产完所有产品后将done标志位设置为1,并通过pthread_cond_signal(&cond);(虽然在这个特定情况下可能不是必需的,因为消费者可能已经在等待)来唤醒可能还在等待的消费者线程。消费者线程在检查到done1buffer0时,会退出循环并结束线程。

其他两种修改方法类似。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落淼喵_G

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值