Linux条件变量:线程同步的利器

Linux 条件变量:线程同步的利器

条件变量(Condition Variable)是 POSIX 线程(pthread)库中最重要、最常用的线程间同步原语之一。它与互斥锁(mutex)配合使用,主要解决“等待某个条件成立”的场景,是实现生产者-消费者模型线程池任务等待状态变更通知等经典模式的首选工具。

一、条件变量的核心思想

条件变量的核心语义只有一句话:

“某个线程可以挂起(等待),直到另一个线程通知它‘条件可能已经满足了’”

不保存任何状态,只负责唤醒等待

条件变量必须和互斥锁一起使用,这是铁律。

最经典的使用模式(伪代码):

pthread_mutex_t mutex;
pthread_cond_t cond;

// 等待条件成立
pthread_mutex_lock(&mutex);
while (条件不满足) {
    pthread_cond_wait(&cond, &mutex);  // 释放锁 → 等待 → 被唤醒后重新加锁
}
... // 条件满足,处理业务
pthread_mutex_unlock(&mutex);

// 通知等待者
pthread_mutex_lock(&mutex);
... // 修改条件
pthread_cond_signal(&cond);   // 唤醒一个
// 或 pthread_cond_broadcast(&cond);  // 唤醒全部
pthread_mutex_unlock(&mutex);

二、条件变量的核心 API(全家桶)

函数作用是否阻塞必须持有锁?典型使用场景
pthread_cond_init初始化条件变量创建时调用
pthread_cond_destroy销毁条件变量不再使用时
pthread_cond_wait等待条件变量被通知(释放锁)核心等待函数
pthread_cond_timedwait带超时的等待需要超时保护的等待
pthread_cond_signal唤醒至少一个等待线程建议持有单消费者或无差别唤醒
pthread_cond_broadcast唤醒所有等待线程建议持有多消费者、状态广播

三、最经典的正确写法(必须掌握)

1. 生产者-消费者模型(单生产者多消费者)

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

#define BUFFER_SIZE 10

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_not_empty = PTHREAD_COND_INITIALIZER;

int buffer[BUFFER_SIZE];
int count = 0;
int in = 0, out = 0;

void* producer(void* arg) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&mutex);

        // 等待不满
        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond_not_full, &mutex);
        }

        buffer[in] = i;
        in = (in + 1) % BUFFER_SIZE;
        count++;

        printf("生产: %d  (count=%d)\n", i, count);

        pthread_cond_broadcast(&cond_not_empty);  // 通知消费者
        pthread_mutex_unlock(&mutex);

        usleep(rand() % 100000);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&mutex);

        // 等待不空
        while (count == 0) {
            pthread_cond_wait(&cond_not_empty, &mutex);
        }

        int data = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        count--;

        printf("消费: %d  (count=%d)\n", data, count);

        pthread_cond_signal(&cond_not_full);  // 通知生产者
        pthread_mutex_unlock(&mutex);

        usleep(rand() % 200000);
    }
    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);

    return 0;
}

2. 为什么 while 而不是 if?(虚假唤醒)

虚假唤醒(spurious wakeup) 是条件变量的天然特性:

  • 即使没人 signal,线程也可能被系统唤醒
  • 被唤醒后条件不一定成立

因此必须用 while 循环检查条件,这是 POSIX 标准强烈推荐的写法:

while (条件不满足) {
    pthread_cond_wait(&cond, &mutex);
}

永远不要用 if

四、条件变量常见误区与正确姿势

误区后果正确做法
用 if 而不是 while 判断条件虚假唤醒导致逻辑错误永远用 while
在 signal/broadcast 后不解锁可能导致被唤醒线程立即再次阻塞先 signal,再 unlock(或一起在锁内)
忘记初始化条件变量未定义行为总是 pthread_cond_init
在不持有锁的情况下 signal可能丢失唤醒建议在持有锁时 signal
使用同一个 cond 变量做多种等待唤醒错乱每种等待条件用独立的 cond
忘记销毁 cond 和 mutex资源泄漏程序结束前 destroy

五、进阶技巧与工程实践

  1. 带超时的等待(防止永久阻塞)
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5;  // 超时 5 秒

pthread_cond_timedwait(&cond, &mutex, &ts);
  1. 多条件等待(经典写法)
pthread_mutex_lock(&mutex);
while (!ready || !data_available) {
    if (!ready) pthread_cond_wait(&cond_ready, &mutex);
    if (!data_available) pthread_cond_wait(&cond_data, &mutex);
}
  1. 条件变量 + 互斥锁的最佳实践命名
pthread_mutex_t queue_mutex;
pthread_cond_t queue_not_empty;
pthread_cond_t queue_not_full;
  1. 线程池中任务等待的典型用法
// 线程池 worker 线程
while (1) {
    pthread_mutex_lock(&pool->mutex);
    while (queue_empty(pool->task_queue)) {
        pthread_cond_wait(&pool->cond, &pool->mutex);
    }
    task = dequeue(pool->task_queue);
    pthread_mutex_unlock(&pool->mutex);

    task->func(task->arg);
}

六、总结:一句话记住条件变量

条件变量不是用来“保存条件”的,而是用来“通知条件可能变了”的。
它必须搭配互斥锁使用,等待时用 while + cond_wait通知时用 signal 或 broadcast

掌握了条件变量 + 互斥锁,你就真正掌握了线程同步的精髓,几乎所有复杂的多线程协作模式(线程池、任务队列、状态机、缓存失效通知、barrier 等)都可以基于它构建。

如果你想看更复杂的例子(比如多生产者多消费者、带优先级的条件等待、实现线程安全的队列、条件变量在实际项目中的封装),可以直接告诉我,我可以继续给出完整代码和详细说明。

文章已创建 4391

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部