blj28

导航

条件变量pthread_cond_t +线程同步机制




1. 概念
  • 条件变量是线程可用的另一种同步机制
  • 条件变量给多个线程提供了一个会合的场所
  • 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
  • 条件变量是线程中的东西,就是等待某一条件的发生,和信号一样

2. 使用场景

  • 条件变量要与互斥量一起使用,条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量
  • 其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件

3. 数据类型

  • pthread_cond_t

条件变量 同步程序运行的代码

deno1

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

#define MAX_BUF 5
int buf_cnt = 0;

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_full  = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_empty = PTHREAD_COND_INITIALIZER;

// 生产者线程函数
void *producer(void *arg) {
    for (;;) {
        pthread_mutex_lock(&mtx);
        // 缓冲区满则等待
        while (buf_cnt >= MAX_BUF) {
            pthread_cond_wait(&cond_full, &mtx);
        }
        buf_cnt++;
        printf("生产,当前数量:%d\n", buf_cnt);
        // 通知消费者有数据
        pthread_cond_signal(&cond_empty);
        pthread_mutex_unlock(&mtx);
    }
}

// 消费者线程函数
void *consumer(void *arg) {
    for (;;) {
        pthread_mutex_lock(&mtx);
        // 缓冲区空则等待
        while (buf_cnt <= 0) {
            pthread_cond_wait(&cond_empty, &mtx);
        }
        buf_cnt--;
        printf("消费,当前数量:%d\n", buf_cnt);
        // 通知生产者有空位
        pthread_cond_signal(&cond_full);
        pthread_mutex_unlock(&mtx);
    }
}

// 【新增】C程序入口main函数
int main() {
    pthread_t prod_thread;  // 生产者线程标识符
    pthread_t cons_thread;  // 消费者线程标识符

    // 1. 创建生产者线程
    pthread_create(&prod_thread, NULL, producer, NULL);
    // 2. 创建消费者线程
    pthread_create(&cons_thread, NULL, consumer, NULL);

    // 3. 等待线程执行完毕(避免主线程提前退出)
    pthread_join(prod_thread, NULL);
    pthread_join(cons_thread, NULL);

    // 4. 销毁互斥锁和条件变量,释放系统资源
    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond_full);
    pthread_cond_destroy(&cond_empty);

    return 0;
}

demo2

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>

// ==================== 共享状态与同步原语 ====================
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  cond  = PTHREAD_COND_INITIALIZER;

static volatile bool data_available = false; // 数据就绪标志
static volatile bool should_exit    = false; // 线程退出标志

// 模拟数据缓冲区
static int shared_data = 0;

// ==================== 消费者线程 ====================
static void *FS_data_deal_thread(void *arg)
{
    prctl(PR_SET_NAME, "FreeSerial");

    while (1)
    {
        pthread_testcancel();

        // 标准范式:在锁内检查条件,不满足则等待
        pthread_mutex_lock(&mutex);
        while (!data_available && !should_exit)
        {
            pthread_cond_wait(&cond, &mutex);
        }

        // 被唤醒后,优先检查退出信号
        if (should_exit)
        {
            pthread_mutex_unlock(&mutex);
            break;  //----Main函数中将should_exit置1 逻辑到这里退出消费者的while循环,因此该线程处于退出状态,主函数没有自动退出的点
        }

        // 消费数据
        shared_data = 0;
        data_available = false;
        pthread_mutex_unlock(&mutex);

        // 模拟数据处理耗时
        printf("[Consumer] Data processed successfully.\n");
    }

    printf("[Consumer] Thread exiting gracefully.\n");
    return NULL;
}

// ==================== 生产者线程 ====================
static void *FS_data_produce_thread(void *arg)
{
    prctl(PR_SET_NAME, "FreeSerialProducer");
    int count = 0;

    while (1)
    {
        pthread_testcancel();

        // 模拟数据采集/生成过程
        usleep(100000); // 100ms 生产间隔
        count++;

        // 原子性地更新共享状态并发送通知
        pthread_mutex_lock(&mutex);
        shared_data = count;
        data_available = true;
        pthread_cond_signal(&cond); // 精准唤醒消费者
        pthread_mutex_unlock(&mutex);
    }

    return NULL;
}

// ==================== 主函数 ====================
int main(void)
{
    pthread_t producer_tid, consumer_tid;

    printf("[Main] Starting producer-consumer threads...\n");

    // 创建线程
    if (pthread_create(&producer_tid, NULL, FS_data_produce_thread, NULL) != 0)
    {
        perror("Failed to create producer thread");
        return EXIT_FAILURE;
    }

    if (pthread_create(&consumer_tid, NULL, FS_data_deal_thread, NULL) != 0)
    {
        perror("Failed to create consumer thread");
        return EXIT_FAILURE;
    }

    // 模拟主线程运行一段时间
    sleep(3);

    // ==================== 优雅退出 ====================
    printf("[Main] Initiating graceful shutdown...\n");

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

    // 等待线程安全退出
    pthread_join(producer_tid, NULL);
    pthread_join(consumer_tid, NULL);

    // 清理同步原语
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    printf("[Main] All threads joined. Program exiting.\n");
    return EXIT_SUCCESS;
}

线程同步机制有哪些

 
线程同步机制种类繁多,选择哪种取决于具体的业务场景。在 Linux/POSIX 环境下,主要可以分为以下五大类:

🔒 互斥锁

这是最基础、最常用的同步原语,用于保护临界区,确保同一时刻只有一个线程能访问共享资源。
  • pthread_mutex_t:标准互斥锁。适用于大多数临界区保护场景。
  • pthread_rwlock_t:读写锁。适用于读多写少的场景。允许多个线程同时读,但写操作是独占的,能显著提升并发读性能。
  • pthread_spinlock_t:自旋锁。线程在获取锁失败时忙等待(不释放CPU)。仅适用于临界区极短多核环境,避免上下文切换开销。在单核或长临界区场景下会严重浪费CPU。

🔔 条件变量

用于线程间的事件通知,解决“等待某个条件成立”的问题。它必须与互斥锁配合使用
  • pthread_cond_t:标准条件变量。支持 pthread_cond_wait(等待)、pthread_cond_signal(唤醒一个)、pthread_cond_broadcast(唤醒所有)。
  • 典型场景:生产者-消费者模型、线程池任务队列、主线程等待子线程完成初始化等。
  • 核心要点:必须用 while 循环包裹 wait 以处理虚假唤醒;唤醒后需重新检查条件。

🚦 信号量

用于控制有限资源的并发访问数量,或实现线程间的计数同步
  • sem_t (POSIX 信号量)
    • 命名信号量:可用于进程间同步。
    • 无名信号量:仅用于线程间同步(需共享内存)。
    • 支持 sem_wait(P操作,减1,资源不足则阻塞)、sem_post(V操作,加1,唤醒等待者)。
  • 典型场景:数据库连接池、限流器、生产者-消费者(用信号量替代条件变量+互斥锁的组合)。
  • 与互斥锁的区别:互斥锁强调“所有权”(谁加锁谁解锁),信号量强调“资源计数”(谁都可以释放)。

📢 屏障

用于多线程协同,确保所有线程都到达某个同步点后再一起继续执行。
  • pthread_barrier_t:屏障对象。初始化时指定线程数 N,每个线程调用 pthread_barrier_wait 后阻塞,直到第 N 个线程到达,所有线程同时被释放。
  • 典型场景:并行计算的分阶段同步(如MapReduce的Shuffle阶段)、多线程数据预处理后统一进入计算阶段。
  • 注意:屏障是一次性的,若需重复使用,需调用 pthread_barrier_init 重新初始化。

📬 其他同步机制

表格
机制核心用途适用场景
原子操作 无锁同步,保证操作的原子性 计数器、标志位、轻量级状态机
pthread_once 确保初始化代码只执行一次 单例模式、全局资源初始化
pthread_join 等待线程结束并回收资源 主线程等待子线程完成
pthread_cancel 异步/延迟取消线程 线程安全退出(需配合取消点)
消息队列 线程间传递结构化数据 解耦生产者和消费者,避免共享内存竞争

📌 选型指南

  • 保护共享变量 → 互斥锁(读多写少用读写锁,极短临界区用自旋锁)
  • 等待事件/条件 → 条件变量 + 互斥锁
  • 控制资源数量 → 信号量
  • 多线程阶段同步 → 屏障
  • 轻量级计数/标志 → 原子操作
  • 线程间传数据 → 消息队列(避免共享内存)

⚠️ 重要提醒

  • 避免死锁:始终按固定顺序加锁;使用 pthread_mutex_trylock 或带超时的锁;避免在持锁时调用可能阻塞的函数。
  • 避免活锁:自旋锁仅在临界区极短时使用;条件变量必须用 while 循环。
  • 资源清理:线程退出前务必释放所有锁、销毁同步原语,避免内存泄漏。
  • 调试工具:使用 valgrind --tool=helgrindThreadSanitizer 检测数据竞争和死锁。

posted on 2026-07-18 15:37  bailinjun  阅读(2)  评论(0)    收藏  举报