11. 条件变量.md 10.0 KB

条件变量 (Condition Variable)

条件变量概念

条件变量是线程间同步的等待/通知机制,用于在线程之间传递事件通知。它必须与互斥锁配合使用。

核心思想:一个线程等待某个条件成立,另一个线程在条件成立时发出通知。


基本操作

初始化与销毁

#include <pthread.h>

// 动态初始化
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

// 静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 销毁
int pthread_cond_destroy(pthread_cond_t *cond);

等待与通知

// 等待条件变量(原子释放锁+等待通知)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

// 通知一个等待线程
int pthread_cond_signal(pthread_cond_t *cond);

// 通知所有等待线程
int pthread_cond_broadcast(pthread_cond_t *cond);

带超时的等待

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

pthread_cond_wait 详解

pthread_cond_wait 的执行过程:

  1. 原子地释放互斥锁
  2. 线程进入等待状态
  3. 被通知后,重新获取互斥锁
  4. 返回调用者

关键点

  • 必须在持有互斥锁时调用
  • 返回时会重新获取互斥锁
  • 可能出现虚假唤醒,需要循环检查条件

错误使用示例

// 错误:没有持有互斥锁
pthread_cond_wait(&cond, &mutex);  // 未定义行为

// 错误:没有循环检查条件
pthread_mutex_lock(&mutex);
if (!condition) {
    pthread_cond_wait(&cond, &mutex);
}
// 处理条件  // 可能虚假唤醒导致错误
pthread_mutex_unlock(&mutex);

正确使用模式

pthread_mutex_lock(&mutex);
while (!condition) {
    pthread_cond_wait(&cond, &mutex);
}
// 处理条件
pthread_mutex_unlock(&mutex);

虚假唤醒 (Spurious Wakeup)

什么是虚假唤醒

条件变量可能在没有 signalbroadcast 的情况下唤醒线程,这是 POSIX 标准允许的行为。

处理方法

使用 while 循环检查条件,而不是 if

// 错误:可能虚假唤醒
if (count > 0) {
    count--;
}

// 正确:循环检查
while (count <= 0) {
    pthread_cond_wait(&cond, &mutex);
}
count--;

经典问题:生产者-消费者完整实现

有界缓冲区实现

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

#define BUFFER_SIZE 5
#define NUM_ITEMS 20

typedef struct {
    int buffer[BUFFER_SIZE];
    int in;  // 生产者写入位置
    int out; // 消费者读取位置
    int count;  // 当前缓冲区中的物品数量

    pthread_mutex_t mutex;
    pthread_cond_t not_full;   // 缓冲区不满条件
    pthread_cond_t not_empty;  // 缓冲区不空条件
} BoundedBuffer;

void buffer_init(BoundedBuffer* buf) {
    buf->in = 0;
    buf->out = 0;
    buf->count = 0;
    pthread_mutex_init(&buf->mutex, NULL);
    pthread_cond_init(&buf->not_full, NULL);
    pthread_cond_init(&buf->not_empty, NULL);
}

void buffer_destroy(BoundedBuffer* buf) {
    pthread_mutex_destroy(&buf->mutex);
    pthread_cond_destroy(&buf->not_full);
    pthread_cond_destroy(&buf->not_empty);
}

void buffer_produce(BoundedBuffer* buf, int item) {
    pthread_mutex_lock(&buf->mutex);

    // 等待缓冲区不满
    while (buf->count >= BUFFER_SIZE) {
        printf("Producer: buffer full, waiting...\n");
        pthread_cond_wait(&buf->not_full, &buf->mutex);
    }

    // 生产物品
    buf->buffer[buf->in] = item;
    buf->in = (buf->in + 1) % BUFFER_SIZE;
    buf->count++;

    printf("Producer: produced %d, count: %d\n", item, buf->count);

    // 通知消费者有数据
    pthread_cond_signal(&buf->not_empty);
    pthread_mutex_unlock(&buf->mutex);
}

int buffer_consume(BoundedBuffer* buf) {
    pthread_mutex_lock(&buf->mutex);

    // 等待缓冲区不空
    while (buf->count <= 0) {
        printf("Consumer: buffer empty, waiting...\n");
        pthread_cond_wait(&buf->not_empty, &buf->mutex);
    }

    // 消费物品
    int item = buf->buffer[buf->out];
    buf->out = (buf->out + 1) % BUFFER_SIZE;
    buf->count--;

    printf("Consumer: consumed %d, count: %d\n", item, buf->count);

    // 通知生产者有空间
    pthread_cond_signal(&buf->not_full);
    pthread_mutex_unlock(&buf->mutex);

    return item;
}

void* producer(void* arg) {
    BoundedBuffer* buf = (BoundedBuffer*)arg;

    for (int i = 0; i < NUM_ITEMS; i++) {
        buffer_produce(buf, i);
        usleep(100000);  // 100ms
    }

    return NULL;
}

void* consumer(void* arg) {
    BoundedBuffer* buf = (BoundedBuffer*)arg;

    for (int i = 0; i < NUM_ITEMS; i++) {
        int item = buffer_consume(buf);
        usleep(150000);  // 150ms
    }

    return NULL;
}

int main() {
    BoundedBuffer buf;
    buffer_init(&buf);

    pthread_t prod_thread, cons_thread;
    pthread_create(&prod_thread, NULL, producer, &buf);
    pthread_create(&cons_thread, NULL, consumer, &buf);

    pthread_join(prod_thread, NULL);
    pthread_join(cons_thread, NULL);

    buffer_destroy(&buf);
    return 0;
}

编译命令

gcc -o bounded_buffer bounded_buffer.c -pthread

嵌入式场景:事件驱动架构

事件通知系统

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

#define NUM_EVENTS 10

typedef enum {
    EVENT_TYPE_A,
    EVENT_TYPE_B,
    EVENT_TYPE_C,
    EVENT_TYPE_EXIT
} EventType;

typedef struct {
    EventType type;
    int data;
} Event;

typedef struct {
    Event events[NUM_EVENTS];
    int head;
    int tail;
    int count;

    pthread_mutex_t mutex;
    pthread_cond_t event_available;
    int running;
} EventQueue;

void queue_init(EventQueue* queue) {
    queue->head = 0;
    queue->tail = 0;
    queue->count = 0;
    queue->running = 1;
    pthread_mutex_init(&queue->mutex, NULL);
    pthread_cond_init(&queue->event_available, NULL);
}

void queue_destroy(EventQueue* queue) {
    queue->running = 0;
    pthread_cond_broadcast(&queue->event_available);
    pthread_mutex_destroy(&queue->mutex);
    pthread_cond_destroy(&queue->event_available);
}

int queue_push(EventQueue* queue, Event* event) {
    pthread_mutex_lock(&queue->mutex);

    if (queue->count >= NUM_EVENTS) {
        pthread_mutex_unlock(&queue->mutex);
        return -1;
    }

    queue->events[queue->tail] = *event;
    queue->tail = (queue->tail + 1) % NUM_EVENTS;
    queue->count++;

    pthread_cond_signal(&queue->event_available);
    pthread_mutex_unlock(&queue->mutex);
    return 0;
}

int queue_pop(EventQueue* queue, Event* event) {
    pthread_mutex_lock(&queue->mutex);

    while (queue->count == 0 && queue->running) {
        pthread_cond_wait(&queue->event_available, &queue->mutex);
    }

    if (!queue->running && queue->count == 0) {
        pthread_mutex_unlock(&queue->mutex);
        return -1;
    }

    *event = queue->events[queue->head];
    queue->head = (queue->head + 1) % NUM_EVENTS;
    queue->count--;

    pthread_mutex_unlock(&queue->mutex);
    return 0;
}

void* event_handler(void* arg) {
    EventQueue* queue = (EventQueue*)arg;
    Event event;

    while (queue_pop(queue, &event) == 0) {
        switch (event.type) {
            case EVENT_TYPE_A:
                printf("Handler: Event A with data %d\n", event.data);
                break;
            case EVENT_TYPE_B:
                printf("Handler: Event B with data %d\n", event.data);
                break;
            case EVENT_TYPE_C:
                printf("Handler: Event C with data %d\n", event.data);
                break;
            case EVENT_TYPE_EXIT:
                printf("Handler: Exit event received\n");
                return NULL;
            default:
                printf("Handler: Unknown event type\n");
        }
    }

    return NULL;
}

void* event_generator(void* arg) {
    EventQueue* queue = (EventQueue*)arg;
    Event event;

    for (int i = 0; i < 5; i++) {
        event.type = EVENT_TYPE_A;
        event.data = i;
        queue_push(queue, &event);
        usleep(100000);

        event.type = EVENT_TYPE_B;
        event.data = i * 10;
        queue_push(queue, &event);
        usleep(100000);
    }

    // 发送退出事件
    event.type = EVENT_TYPE_EXIT;
    queue_push(queue, &event);

    return NULL;
}

int main() {
    EventQueue queue;
    queue_init(&queue);

    pthread_t handler_thread, generator_thread;
    pthread_create(&handler_thread, NULL, event_handler, &queue);
    pthread_create(&generator_thread, NULL, event_generator, &queue);

    pthread_join(generator_thread, NULL);
    pthread_join(handler_thread, NULL);

    queue_destroy(&queue);
    return 0;
}

编译命令

gcc -o event_queue event_queue.c -pthread

注意事项

  1. 必须配合互斥锁使用:条件变量不能独立使用
  2. 虚假唤醒:始终使用 while 循环检查条件
  3. 通知丢失signal 可能丢失,需要确保条件被检查
  4. 销毁时机:确保没有线程在等待时销毁条件变量
  5. 性能考虑:避免不必要的唤醒

面试要点

Q1: 为什么条件变量需要与互斥锁配合使用?

A:

  • 条件检查需要原子性
  • 防止信号丢失
  • 确保条件检查的正确性

Q2: pthread_cond_signalpthread_cond_broadcast 的区别?

A:

  • signal:唤醒一个等待线程
  • broadcast:唤醒所有等待线程
  • 选择取决于具体需求

Q3: 什么是虚假唤醒?如何处理?

A:

  • 虚假唤醒:条件变量在没有通知的情况下唤醒线程
  • 处理:使用 while 循环检查条件

Q4: 如何避免条件变量的通知丢失?

A:

  1. 先检查条件,再决定是否等待
  2. 使用 while 循环确保条件被检查
  3. 在修改共享变量后发送通知

Q5: 条件变量在嵌入式系统中的应用场景?

A:

  1. 事件驱动架构
  2. 生产者-消费者模型
  3. 线程池管理
  4. 资源池管理

相关链接

  • [[10. 互斥锁]]
  • [[8. 线程基础]]