前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >嵌入式Linux:线程同步(条件变量)

嵌入式Linux:线程同步(条件变量)

作者头像
不脱发的程序猿
发布2025-01-14 14:20:05
发布2025-01-14 14:20:05
11400
代码可运行
举报
运行总次数:0
代码可运行

在Linux环境下,条件变量(Condition Variables)是一种线程同步机制,允许线程在某个条件未满足时进入等待状态,并在其他线程修改共享资源或条件后通知它们。

条件变量和互斥锁通常一起使用,以保证对共享资源的安全访问。

通过条件变量,线程可以避免忙等待(busy-waiting),从而提高效率。

条件变量使得线程可以通过以下方式同步:

  • 等待某个条件满足:当某个线程在等待某个条件时,它可以进入阻塞状态,并释放持有的互斥锁,以允许其他线程操作共享资源。
  • 通知条件满足:当其他线程改变了共享资源的状态,且满足了等待线程的条件,它可以通过发送信号(signal)来通知那些正在等待的线程,使它们被唤醒并继续执行。

条件变量通常与互斥锁结合使用,因为在检查或修改某些共享资源时,需要保护这些资源的并发访问,防止竞争条件。

互斥锁负责保护共享资源,条件变量负责在线程间传递状态信息。

具体步骤如下:

  • 线程通过互斥锁访问共享资源。
  • 当条件未满足时,线程通过条件变量进入等待,并释放互斥锁,允许其他线程继续操作资源。
  • 其他线程修改共享资源并发出条件满足的信号,通知条件变量唤醒等待线程。
  • 被唤醒的线程重新获得互斥锁并检查条件是否满足,如果满足则继续执行,否则继续等待。

条件变量的使用步骤:

  • 初始化条件变量和互斥锁。
  • 在线程中使用互斥锁对共享资源进行保护。
  • 如果条件未满足,调用pthread_cond_wait()使线程进入阻塞状态,同时释放互斥锁,等待其他线程通知条件满足。
  • 其他线程修改共享资源后,调用pthread_cond_signal()或pthread_cond_broadcast()通知等待线程。
  • 被唤醒的线程重新获得互斥锁并继续检查条件。

1

条件变量的初始化和销毁

条件变量使用pthread_cond_t数据类型来表示,和互斥锁类似,条件变量在使用前必须初始化。

可以通过两种方式进行初始化:

1、使用宏PTHREAD_COND_INITIALIZER,类似于互斥锁的静态初始化方式:

代码语言:javascript
代码运行次数:0
复制
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2、使用pthread_cond_init()函数动态初始化条件变量:

代码语言:javascript
代码运行次数:0
复制
pthread_cond_init(&cond, NULL); // attr 为 NULL 时使用默认属性

参数

  • cond: 指向待初始化的条件变量对象,类型为 pthread_cond_t*。
  • attr: 条件变量的属性,可以为 NULL 表示使用默认属性。

返回值

  • 成功返回 0;
  • 失败返回非零错误码,如 EINVAL(无效属性)。

当不再使用条件变量时,应使用pthread_cond_destroy()进行销毁,以释放系统资源,销毁前需要确保没有线程在等待该条件变量。

代码语言:javascript
代码运行次数:0
复制
pthread_cond_destroy(&cond);

参数

  • cond: 指向待销毁的条件变量对象。

返回值

  • 成功返回 0;
  • 失败返回非零错误码,如 EBUSY(有线程在等待该条件变量)。

注意事项

  • 只能销毁已经初始化的条件变量。
  • 条件变量销毁时,不能有线程在等待它,否则将导致未定义行为。

2

发送信号和等待条件变量

条件变量的核心功能就是发送信号和等待条件。

在多线程程序中,线程通过pthread_cond_wait()等待条件,而其他线程通过pthread_cond_signal()或pthread_cond_broadcast()发出信号。

pthread_cond_wait():使线程等待条件满足,并在等待期间释放互斥锁。该函数会阻塞调用线程,直到另一个线程调用 pthread_cond_signal() 或 pthread_cond_broadcast() 通知该条件变量。

代码语言:javascript
代码运行次数:0
复制
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

参数

  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。
  • mutex: 互斥锁指针,指向 pthread_mutex_t 类型的互斥锁。在线程进入等待状态时,pthread_cond_wait 会自动释放该互斥锁,并在线程被唤醒时重新加锁。

返回值

  • 成功返回 0;
  • 失败返回非零错误码。

注意事项

  • 在调用 pthread_cond_wait() 之前,必须先锁住互斥锁,以避免条件检查和等待之间的竞争。
  • 线程被唤醒时,会重新锁住传入的互斥锁。

pthread_cond_signal():用于通知至少一个等待该条件变量的线程,使其从 pthread_cond_wait() 的阻塞状态中唤醒。如果没有线程在等待条件变量,该信号会被丢弃。

代码语言:javascript
代码运行次数:0
复制
int pthread_cond_signal(pthread_cond_t *cond);

参数

  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。

返回值

  • 成功返回 0;
  • 失败返回非零错误码。

注意事项

  • 该函数只能唤醒一个等待的线程。
  • 如果没有线程等待,信号不会累积,因此没有线程在等待时,调用该函数不会有任何效果。

pthread_cond_broadcast():用于唤醒所有等待该条件变量的线程。适用于需要唤醒多个线程的场景。

代码语言:javascript
代码运行次数:0
复制
int pthread_cond_broadcast(pthread_cond_t *cond);

参数

  • cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。

返回值

  • 成功返回 0;
  • 失败返回非零错误码。

假设有多个消费者线程在等待产品,如果使用pthread_cond_signal(),只有一个线程会被唤醒。

如果我们希望所有消费者都被唤醒,则使用pthread_cond_broadcast()。

但一般来说,pthread_cond_signal()更高效,除非确实需要所有线程都被唤醒。

代码语言:javascript
代码运行次数:0
复制
pthread_cond_signal(&cond);   // 唤醒一个线程
pthread_cond_broadcast(&cond); // 唤醒所有等待的线程

3

条件变量中的判断条件

在使用条件变量时,通常涉及某个共享变量(如上例中的buffer)。

在检测条件时,必须使用while循环而非if语句。这是因为:

  • 虚假唤醒:线程可能被意外唤醒,但条件仍不满足。用while循环可以确保条件再次检查,而不是立即继续执行。
  • 竞争条件:多个线程可能同时被唤醒,但只有一个线程会成功获取锁并修改条件状态。其他线程必须再次等待。

这样设计能确保线程在条件不满足时不会继续执行,从而避免竞争条件带来的问题。

代码语言:javascript
代码运行次数:0
复制
pthread_mutex_lock(&mutex);
while (buffer == 0) {
    pthread_cond_wait(&cond, &mutex);
}

以下示例展示了生产者-消费者模型,其中生产者线程和消费者线程通过条件变量进行同步:

代码语言:javascript
代码运行次数:0
复制
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer = 0;  // 缓冲区,0表示空,1表示有产品

void *producer(void *arg) {
    pthread_mutex_lock(&mutex);  // 加锁
    while (buffer == 1) {
        pthread_cond_wait(&cond, &mutex);  // 缓冲区满,等待
    }
    buffer = 1;  // 生产产品
    printf("Produced an item\n");
    pthread_cond_signal(&cond);  // 通知消费者
    pthread_mutex_unlock(&mutex);  // 解锁
    return NULL;
}

void *consumer(void *arg) {
    pthread_mutex_lock(&mutex);  // 加锁
    while (buffer == 0) {
        pthread_cond_wait(&cond, &mutex);  // 缓冲区空,等待
    }
    buffer = 0;  // 消费产品
    printf("Consumed an item\n");
    pthread_cond_signal(&cond);  // 通知生产者
    pthread_mutex_unlock(&mutex);  // 解锁
    return NULL;
}

int main() {
    pthread_t prod, cons;

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

    // 等待线程执行完毕
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);

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

    return 0;
}

Linux中的条件变量是线程同步的强大工具,允许线程等待特定条件满足后再执行操作,避免了无效的忙等待。

通过与互斥锁协作,条件变量可以有效地协调线程之间对共享资源的访问,保证并发环境下的安全性和效率。

  • 条件变量与互斥锁结合使用:条件变量用于等待和通知条件变化,互斥锁则用于保护共享资源的访问。
  • 条件变量不保存状态:如果没有线程在等待条件变量,信号会丢失。
  • pthread_cond_wait()应放在循环中:因为多个线程可能竞争资源,因此从 pthread_cond_wait() 返回后应重新检查条件。
  • 选择合适的通知方式:pthread_cond_signal() 唤醒一个等待线程,pthread_cond_broadcast() 唤醒所有等待线程。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 美男子玩编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档