当前位置: 首页 > news >正文

[OS] Pthread 条件变量

5. Pthread 条件变量

条件变量提供了一种线程之间的同步方式。通过互斥锁来控制线程对数据的访问,而条件变量则允许线程根据数据的实际值来同步。

条件变量的典型用法是:

  • 当一个线程对数据进行修改后,其他等待数据达到特定状态的线程会被唤醒。
  • 条件变量总是与互斥锁一起使用,以确保共享数据的访问安全。

5.1. Pthread 条件变量的声明

条件变量可以通过 pthread_cond_t 类型声明。

示例声明:

pthread_cond_t count_threshold_cv;  // 声明一个条件变量

5.2. Pthread 条件变量的初始化

条件变量在使用之前需要进行初始化,可以使用 pthread_cond_init() 函数。

int pthread_cond_init(pthread_cond_t *condition, const pthread_condattr_t *attr);
  • 参数
    • condition:指向要初始化的条件变量的指针。
    • attr:条件变量的属性,如果为 NULL,则使用默认属性。

示例初始化:

pthread_cond_init(&count_threshold_cv, NULL);  // 使用默认属性初始化条件变量

5.3. Pthread 条件变量的销毁

在条件变量不再使用时,应将其销毁以释放系统资源。

int pthread_cond_destroy(pthread_cond_t *condition);

 示例销毁:

pthread_cond_destroy(&count_threshold_cv);  // 销毁条件变量

5.4. Pthread 条件变量的操作函数

  • pthread_cond_wait(pthread_cond_t *condition, pthread_mutex_t *mutex)

    • 这个函数使调用线程进入等待状态,直到指定的条件变量被信号唤醒。此函数应该在互斥锁被锁住时调用,它会自动释放互斥锁并等待。
  • pthread_cond_signal(pthread_cond_t *condition)

    • 唤醒一个等待在该条件变量上的线程。应在持有互斥锁的情况下调用。
  • pthread_cond_broadcast(pthread_cond_t *condition)

    • 唤醒所有等待在该条件变量上的线程。

5.5 示例代码:使用条件变量实现线程同步

下面的例子展示了如何使用条件变量和互斥锁来实现多个线程的同步操作。

示例代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 10int count = 0;  // 共享变量
int thread_ids[3] = {0, 1, 2};
pthread_mutex_t count_mutex;  // 互斥锁
pthread_cond_t count_threshold_cv;  // 条件变量// 线程函数:计数增加
void *inc_count(void *idp) {int i = 0;int *my_id = (int *)idp;for (i = 0; i < TCOUNT; i++) {pthread_mutex_lock(&count_mutex);  // 锁定互斥锁count++;if (count == COUNT_LIMIT) {// 当 count 达到 COUNT_LIMIT 时,发出条件信号pthread_cond_signal(&count_threshold_cv);}printf("inc_count(): thread %d, count = %d, unlocking mutex\n", *my_id, count);pthread_mutex_unlock(&count_mutex);  // 解锁互斥锁sleep(1);}printf("inc_count(): thread %d, Threshold reached.\n", *my_id);pthread_exit(NULL);
}// 线程函数:监视计数
void *watch_count(void *idp) {int *my_id = (int *)idp;printf("Starting watch_count(): thread %d\n", *my_id);pthread_mutex_lock(&count_mutex);  // 锁定互斥锁while (count < COUNT_LIMIT) {// 等待条件信号,当条件满足时自动锁定互斥锁pthread_cond_wait(&count_threshold_cv, &count_mutex);printf("watch_count(): thread %d Condition signal received.\n", *my_id);}// 当条件满足后进行的操作count += 100;pthread_mutex_unlock(&count_mutex);  // 解锁互斥锁pthread_exit(NULL);
}int main(int argc, char *argv[]) {int i, rc;pthread_t threads[3];pthread_attr_t attr;// 初始化互斥锁和条件变量pthread_mutex_init(&count_mutex, NULL);pthread_cond_init(&count_threshold_cv, NULL);// 显式创建可连接的线程pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);// 创建线程pthread_create(&threads[0], &attr, watch_count, (void *)&thread_ids[0]);pthread_create(&threads[1], &attr, inc_count, (void *)&thread_ids[1]);pthread_create(&threads[2], &attr, inc_count, (void *)&thread_ids[2]);// 等待所有线程完成for (i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);}// 清理资源printf("Main(): Waited on %d threads. Done.\n", NUM_THREADS);pthread_attr_destroy(&attr);pthread_mutex_destroy(&count_mutex);pthread_cond_destroy(&count_threshold_cv);pthread_exit(NULL);return 0;
}
代码解释
  1. 共享变量 count 和 互斥锁 count_mutex

    • count 是所有线程共享的变量,需要通过互斥锁 count_mutex 来保护。
  2. 线程函数 inc_count()

    • 每个线程循环增加 count,并在 count 达到 COUNT_LIMIT 时,调用 pthread_cond_signal() 向条件变量发出信号。
    • 通过 pthread_mutex_lock()pthread_mutex_unlock() 来锁定和解锁共享变量,确保线程安全。
  3. 线程函数 watch_count()

    • 等待 count 达到 COUNT_LIMIT,调用 pthread_cond_wait() 进行等待。当条件满足时,接收到信号,并进行进一步操作。
    • pthread_cond_wait() 会在等待期间释放互斥锁,并在被唤醒时重新获取锁。
  4. 主函数 main()

    • 创建 3 个线程,其中一个是监视线程,两个是计数线程。
    • 使用 pthread_join() 等待所有线程完成任务。
    • 最后清理所有的资源,包括互斥锁和条件变量。
输出分析
Starting watch_count(): thread 0
inc_count(): thread 1, count = 1, unlocking mutex
inc_count(): thread 2, count = 2, unlocking mutex
...
inc_count(): thread 2, count = 10, unlocking mutex
watch_count(): thread 0 Condition signal received.
inc_count(): thread 1, count = 111, unlocking mutex
...
  • 线程 inc_count() 增加 count,并在达到 COUNT_LIMIT 时通过 pthread_cond_signal() 发出信号。
  • 线程 watch_count() 在条件满足时被唤醒,进行相应的操作。
通俗解释

条件变量就像一个警报器,当某个特定条件满足时,系统就会通知等待的线程。例如,线程在增加一个计数器的值,当这个值达到某个阈值时,会通知其他线程“条件达到了”,等待这个条件的线程就会被唤醒并继续执行任务。

在这个例子中,当计数值 count 达到 COUNT_LIMIT,监视线程 watch_count() 就会被唤醒,然后对计数进行进一步处理。

总结

  • 条件变量用于线程间的同步,允许线程根据数据的值来进行操作。
  • pthread_cond_wait() 使线程等待某个条件的满足。
  • pthread_cond_signal()pthread_cond_broadcast() 用于通知等待中的线程。
  • 条件变量需要与互斥锁结合使用,以确保数据的安全访问。

 

条件变量和互斥锁需要协调使用是为了在多线程环境下实现安全、有效的数据同步。这种协调的必要性可以从两个主要方面来理解:互斥保护条件等待。下面我会详细说明条件变量和互斥锁如何配合,及其背后的原因。

1. 互斥保护数据一致性

在多线程编程中,共享资源可能同时被多个线程访问。为了保证数据的正确性,需要使用互斥锁来控制线程对共享数据的访问。互斥锁(mutex)的作用是确保在任意时刻只有一个线程可以访问共享资源,防止数据竞争情况的发生。

举个例子,如果有多个线程试图同时对一个变量进行加减操作,而不使用互斥锁,那么这个变量的值可能会变得不可预测。例如两个线程都读取了相同的变量值然后进行计算,结果覆盖时会互相冲突,从而导致数据的结果错误。

2. 条件变量实现线程同步

条件变量(condition variable)的作用是允许线程在某种特定的条件下进行等待,并在该条件被满足时被唤醒。这为线程之间提供了一种同步的机制,即线程之间可以基于特定条件的改变来协调它们的行为。

  • 为什么需要互斥锁配合条件变量?

    • 防止竞争条件:条件变量通常用来等待某个特定条件的达成,例如等待某个计数器达到某个值。如果我们在不加锁的情况下对这个条件进行检查,就可能发生竞争条件。例如,一个线程可能在检查条件时被另一个线程打断,而后者改变了条件的状态,从而使原先的条件检查变得无效。

    • 保护共享状态的一致性:条件变量与互斥锁一起使用,是为了在修改共享状态以及等待该状态变化时,保证一致性。每当线程要等待某个条件时,必须首先锁住互斥锁,确保没有其他线程在修改该状态。

    • 条件变量需要互斥锁的保护pthread_cond_wait() 函数会自动释放锁并进入等待状态。当条件满足时,它会重新锁定互斥锁并继续执行。这样可以保证在条件变量释放锁期间,其他线程仍然可以安全地访问或修改共享数据。

3. 条件变量与互斥锁如何协调?

  1. 获取互斥锁:在使用条件变量前,线程首先要获取互斥锁(pthread_mutex_lock())。这一步是为了确保共享数据的状态不会被其他线程同时修改。

  2. 检查条件:在锁住互斥锁后,线程检查共享数据是否满足某个条件。通常这部分代码会放在 while 循环中,以确保线程被唤醒后条件依然有效。这是因为在被唤醒后,其他线程可能已经修改了共享数据。

  3. 等待条件:如果条件不满足,线程调用 pthread_cond_wait() 函数。在调用该函数时,互斥锁会被释放,这样其他线程可以继续执行并修改共享数据。当条件满足时,线程被唤醒并重新锁住互斥锁。

  4. 处理共享数据:当线程被唤醒后,再次锁住互斥锁,并继续操作共享数据。

  5. 解锁互斥锁:在操作完成后,线程释放互斥锁(pthread_mutex_unlock())。

4. 举个例子

假设有一个线程负责生产数据,另一个线程负责消费数据,这里就可以使用条件变量和互斥锁进行同步。

  • 生产者线程会填充缓冲区,并在数据准备好之后,通过条件变量通知消费者线程可以读取数据。
  • 消费者线程会等待条件变量,直到生产者线程通知它数据已准备好。为了防止在数据未准备好时进行读取,消费者线程必须先锁住互斥锁,然后等待条件变量。

协调过程如下:

  1. 生产者线程

    • 获取互斥锁。
    • 修改共享数据(例如向缓冲区写入数据)。
    • 发出条件信号(pthread_cond_signal()),通知等待的线程。
    • 释放互斥锁。
  2. 消费者线程

    • 获取互斥锁。
    • 检查缓冲区状态,如果没有数据,则调用 pthread_cond_wait(),并自动释放互斥锁。
    • 当接收到生产者的信号时,pthread_cond_wait() 返回,消费者线程重新锁定互斥锁。
    • 消费数据,并释放互斥锁。

5. 为什么不直接使用互斥锁来同步?

虽然互斥锁可以用来防止共享数据的竞争访问,但它只能确保数据在某个时刻只能被一个线程访问。互斥锁本身并不能实现“等待某个条件成立”的功能,而条件变量则可以帮助实现这种功能。

通过条件变量,线程可以进入等待状态,直到某个特定条件(比如数据已经就绪)被满足。这种方式比不断轮询(即反复检查条件是否满足)更高效,因为轮询会消耗大量的 CPU 资源。而条件变量让线程在条件不满足时“休眠”,从而释放系统资源,等待被唤醒。

 


http://www.mrgr.cn/news/57883.html

相关文章:

  • 见证 RTE 的新篇章丨 RTE 年度场景 Showcase 暨第四届 RTE 创新大赛开幕
  • 【数据结构】包装类简单认识泛型-Java
  • C++学习笔记----9、发现继承的技巧(五)---- 多重继承(1)
  • 工业一体机为软件开发商提供稳定可靠的硬件平台
  • Rust求解八皇后问题
  • 互联网黑话大全-颗粒度对齐
  • 常用设计模式总结
  • VantUI
  • 大厂的JAVA经典面试题-初中级
  • 基于SpringBoot足球场在线预约系统的设计与实现
  • 海王3纯源码
  • 分享一个开源的、自托管的 API 创建工具——Strapi
  • 又是一年 1024
  • 轻松清理 PC 微信文件,释放存储空间
  • C++学习路线(二十三)
  • EureKa是什么?
  • 揭秘.baxia:勒索病毒的隐秘与危害
  • MybatisPlus入门(二)MybatisPlus入门案例
  • 从0开始的数据结构复习 1
  • 电能表预付费系统-标准传输规范(STS)(20)
  • 三周精通FastAPI:10 Cookie 参数 和Cookie 参数模型
  • day-73 找出数组游戏的赢家
  • Java高级Day57-剩余内容补充
  • 编译方法及工具
  • 【无标题】Django转化为exe,app
  • 论文阅读与写作入门