扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

嵌入式中pthread条件变量怎么用?

2025/11/13
720
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

嵌入式Linux开发中,线程同步是绕不开的核心场景——消息队列数据就绪、外设状态变化、多线程资源协作,这些场景下你是否遇到过这些问题?

    用轮询判断条件,CPU占用率飙升;用sleep做延时等待,要么响应不及时,要么浪费资源;多线程协作时出现竞态条件,逻辑错乱;

而pthread条件变量正是解决线程等待-通知场景的最优解——它能让线程在条件不满足时休眠,条件满足时精准唤醒,既保证响应速度,又能最大化降低CPU占用。

一、核心原理

1.1 条件变量的本质

条件变量是pthread库提供的线程间通知机制,用于传递“特定条件已满足”的信号。它本身不存储数据,也不保证互斥,仅负责线程的休眠与唤醒,必须与互斥锁配合使用。它解决了等待某个条件成立这个特定场景的线程同步问题。

pthread库条件变量API

条件变量(Condition Variable)不是锁,而是一个线程等待队列的抽象。它在内核中维护了一个休眠线程链表,当线程调用pthread_cond_wait()时:

    线程从用户态陷入内核态内核将其状态从RUNNING改为WAITING线程被加入条件变量的等待队列调度器切换到其他就绪线程

当其他线程调用pthread_cond_signal()时,内核会从等待队列中选择一个线程,将其状态改为READY,等待调度器分配CPU时间。

1.2 为什么必须和互斥锁搭配?

这个生产者-消费者场景:

错误场景:没有互斥锁保护

检查条件和进入等待这两个操作必须是原子的,否则会出现lost wakeup(唤醒丢失)

正确场景:使用互斥锁保证原子性

互斥锁配合条件变量确保了从"持有锁检查条件"到"释放锁并休眠"这个过程的原子性,生产者在消费者真正进入等待后才能获取锁并发送信号。

1.3 pthread_cond_wait为什么需要传递互斥锁?

这是条件变量设计的核心,本质是实现“解锁-休眠”的原子操作:

    传递互斥锁后,pthread_cond_wait会先自动释放锁,再让线程休眠——避免线程持有锁休眠,导致其他线程无法修改条件。实际pthread_cond_wait的应用例子如:

**pthread_cond_wait()**在内部原子地完成三个动作:

    线程被唤醒后,会先自动重新获取锁,再返回用户态——确保后续检查条件、执行逻辑时,依然是线程安全的。

如果不传递互斥锁,用户需要手动解锁和加锁,但“解锁”和“休眠”之间会存在间隙,必然导致竞态条件。

1.4  pthread_cond_signal和pthread_cond_broadcast的区别?

特性 pthread_cond_signal pthread_cond_broadcast
唤醒范围 唤醒至少一个等待线程(具体哪个由调度器决定) 唤醒所有等待线程
资源开销 低(仅唤醒一个,调度成本低) 高(唤醒所有,可能引发惊群效应)
适用场景 一对一通知(一个线程修改条件,一个线程等待) 一对多通知(一个线程修改条件,多个线程等待)

1.5. 为什么用while而不是if判断条件?

核心原因:虚假唤醒条件变化

    虚假唤醒:操作系统可能在没有收到signal/broadcast的情况下,唤醒等待的线程(如系统调度优化、信号中断等);条件变化:多个等待线程被唤醒后,其中一个线程可能已经修改了条件(如消费了队列中的数据),其他线程的条件已不再满足。
反例(用if导致bug):

正例(while循环):

无论是否是虚假唤醒,都会重新检查条件,确保只有条件真正满足时才执行后续逻辑。

二、使用条件变量的典型步骤

等待方步骤

    加互斥锁(pthread_mutex_lock);循环检查条件(while(condition == false));条件不满足时,调用pthread_cond_wait休眠;被唤醒后,重新检查条件,执行业务逻辑;解锁互斥锁(pthread_mutex_unlock)。

通知方步骤

    加互斥锁(pthread_mutex_lock);修改条件(如:设置flag为true、添加数据到队列);发送通知(pthread_cond_signal或pthread_cond_broadcast);解锁互斥锁(pthread_mutex_unlock)。
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

staticpthread_t s_thread1_id;
staticpthread_t s_thread2_id;
staticvolatileunsignedchar s_thread1_running = 0;
staticvolatileunsignedchar s_thread2_running = 0;

staticpthread_mutex_t s_mutex;
staticpthread_cond_t s_cond;
staticint s_cnt = 0;
staticint s_processed = 0;  // 标记是否已处理,避免重复处理

// 通知方:
// 1. 加互斥锁(pthread_mutex_lock);
// 2. 修改条件(如:设置flag为true、添加数据到队列);
// 3. 发送通知(pthread_cond_signal或pthread_cond_broadcast);
// 4. 解锁互斥锁(pthread_mutex_unlock)。
void *thread1_fun(void *arg)
{
    s_thread1_running = 1;
    while (s_thread1_running)  
    {
        pthread_mutex_lock(&s_mutex);    // 加锁
        s_cnt++;  // 修改条件
        
        if (s_cnt % 5 == 0)
        {
            s_processed = 0;  // 重置处理标志
            pthread_cond_signal(&s_cond);  // 发送通知唤醒等待线程
            printf("[%s] s_cnt = %d, 发送信号n", __FUNCTION__, s_cnt);
        }
        else
        {
            printf("[%s] s_cnt = %dn", __FUNCTION__, s_cnt);
        }
        
        pthread_mutex_unlock(&s_mutex);  // 解锁
        usleep(100 * 1000);
    }
    
    pthread_exit(NULL);
}

// 等待方:
// 1. 加互斥锁(pthread_mutex_lock);
// 2. 循环检查条件(while(condition == false));
// 3. 条件不满足时,调用pthread_cond_wait休眠;
// 4. 被唤醒后,重新检查条件,执行业务逻辑;
// 5. 解锁互斥锁(pthread_mutex_unlock)。
void *thread2_fun(void *arg)
{
    s_thread2_running = 1;
    while (s_thread2_running)
    {
        pthread_mutex_lock(&s_mutex);    // 加锁
        
        // 等待条件:s_cnt能被5整除且未被处理
        while (s_cnt % 5 != 0 || s_processed)
        {
            pthread_cond_wait(&s_cond, &s_mutex);   // 等待条件变量
        }
        
        // 条件满足,处理数据
        printf("[%s] s_cnt = %d, 条件满足,开始处理n", __FUNCTION__, s_cnt);
        s_processed = 1;  // 标记已处理
        
        pthread_mutex_unlock(&s_mutex);  // 解锁
        usleep(200 * 1000);
    }
    
    pthread_exit(NULL);
}

int main(void)
{
    int ret = 0;

    // 创建互斥量
    ret = pthread_mutex_init(&s_mutex, NULL);
    if (ret != 0)
    {
        printf("pthread_mutex_init error!n");
        exit(EXIT_FAILURE);
    }

    // 创建条件变量
    ret = pthread_cond_init(&s_cond, NULL);
    if (ret != 0)
    {
        printf("pthread_cond_init error!n");
        exit(EXIT_FAILURE);
    }

    // 创建线程1(通知方/生产者)
    ret = pthread_create(&s_thread1_id, NULL, thread1_fun, NULL);
    if (ret != 0)
    {
        printf("thread1_create error!n");
        exit(EXIT_FAILURE);
    }

    // 创建线程2(等待方/消费者)
    ret = pthread_create(&s_thread2_id, NULL, thread2_fun, NULL);
    if (ret != 0)
    {
        printf("thread2_create error!n");
        exit(EXIT_FAILURE);
    }

    // 运行10秒后退出
    sleep(10);
    
    s_thread1_running = 0;
    s_thread2_running = 0;
    
    pthread_join(s_thread1_id, NULL);
    pthread_join(s_thread2_id, NULL);
    
    pthread_mutex_destroy(&s_mutex);
    pthread_cond_destroy(&s_cond);
    
    printf("程序正常退出n");
    return0;
}

实际应用注意事项:

不要在持有多个锁时调用pthread_cond_wait——唤醒后重新加锁的顺序可能导致死锁;

确保条件变量和互斥锁的生命周期一致——避免在销毁后仍有线程调用wait/signal;

避免“惊群效应”——broadcast会唤醒所有等待线程,若线程数量多,可拆分条件变量,按功能分组唤醒。

三、总结

pthread条件变量的核心价值是“高效的等待-通知”,解决了轮询和sleep的资源浪费问题。其使用的关键的是把握3点:互斥锁的原子配合while循环检查条件signal/broadcast的场景匹配

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!