在Linux多线程开发过程中,条件变量和信号量是两种核心的线程同步机制,二者虽然都能实现线程间的协作与等待,但在设计目标、工作逻辑和适用场景上存在明显差异,理解这些差异能帮助开发者更合理地选择同步方案。
核心定义与本质差异
条件变量的核心作用是等待某个条件成立,它需要和互斥锁配合使用,本质是一个线程等待队列,当某个共享数据的状态满足特定条件时,通知等待的线程继续执行。
信号量的本质是一个计数器,用来控制同时访问某个共享资源的线程数量,计数器的值表示当前可用的资源数量,线程获取资源时计数器减1,释放资源时计数器加1。
工作原理对比
条件变量的工作流程
条件变量的使用必须绑定一个互斥锁,流程如下:
- 线程先获取互斥锁,检查共享条件是否满足
- 若条件不满足,调用等待函数释放互斥锁并进入阻塞状态,放入条件变量的等待队列
- 其他线程修改共享条件后,调用唤醒函数通知等待队列中的线程
- 被唤醒的线程重新获取互斥锁,再次检查条件是否满足,满足则继续执行
信号量的工作流程
信号量独立工作,不需要绑定互斥锁,流程如下:
- 初始化信号量时设置初始计数器值,代表可用资源数量
- 线程需要访问资源时调用P操作(等待操作),计数器减1,若计数器小于0则线程阻塞
- 线程使用完资源后调用V操作(释放操作),计数器加1,若计数器小于等于0则唤醒一个阻塞的线程
基础使用示例
条件变量使用示例
以下代码实现了一个简单的生产者消费者模型,生产者生产数据后通知消费者,消费者等待数据就绪后消费:
#include <stdio.h>
#include <pthread.h>
// 定义互斥锁和条件变量
pthread_mutex_t mutex;
pthread_cond_t cond;
int data_ready = 0; // 共享条件:数据是否就绪
// 生产者线程函数
void* producer(void* arg) {
pthread_mutex_lock(&mutex);
data_ready = 1; // 修改共享条件
printf("生产者生产数据完成n");
pthread_cond_signal(&cond); // 唤醒等待条件变量的线程
pthread_mutex_unlock(&mutex);
return NULL;
}
// 消费者线程函数
void* consumer(void* arg) {
pthread_mutex_lock(&mutex);
// 循环检查条件,避免虚假唤醒
while (data_ready == 0) {
printf("消费者等待数据n");
pthread_cond_wait(&cond, &mutex); // 释放互斥锁并等待
}
printf("消费者消费数据n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t prod_tid, cons_tid;
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
// 创建线程
pthread_create(&cons_tid, NULL, consumer, NULL);
sleep(1); // 让消费者先启动进入等待
pthread_create(&prod_tid, NULL, producer, NULL);
// 等待线程结束
pthread_join(prod_tid, NULL);
pthread_join(cons_tid, NULL);
// 销毁同步对象
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
信号量使用示例
以下代码使用信号量控制同时访问共享资源的线程数量,初始信号量设为2,最多允许2个线程同时访问资源:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem; // 定义信号量
// 线程函数
void* worker(void* arg) {
int thread_id = *(int*)arg;
sem_wait(&sem); // P操作:获取资源,计数器减1
printf("线程%d获取资源,开始工作n", thread_id);
sleep(2); // 模拟工作耗时
printf("线程%d工作完成,释放资源n", thread_id);
sem_post(&sem); // V操作:释放资源,计数器加1
return NULL;
}
int main() {
pthread_t tids[5];
int thread_ids[5];
// 初始化信号量,初始值为2,代表2个可用资源
sem_init(&sem, 0, 2);
// 创建5个线程
for (int i = 0; i < 5; i++) {
thread_ids[i] = i + 1;
pthread_create(&tids[i], NULL, worker, &thread_ids[i]);
}
// 等待所有线程结束
for (int i = 0; i < 5; i++) {
pthread_join(tids[i], NULL);
}
// 销毁信号量
sem_destroy(&sem);
return 0;
}
关键差异总结
| 对比维度 | 条件变量 | 信号量 |
|---|---|---|
| 核心作用 | 等待特定条件成立,用于线程间的条件通知 | 控制并发访问资源的线程数量,用于资源计数 |
| 依赖组件 | 必须配合互斥锁使用 | 独立使用,无需绑定其他同步对象 |
| 计数器特性 | 无计数器,仅维护等待队列 | 自带计数器,记录可用资源数量 |
| 唤醒逻辑 | 可以唤醒一个或所有等待线程 | 每次V操作最多唤醒一个阻塞线程 |
| 适用场景 | 生产者消费者模型、等待某个状态变化 | 限流、控制并发数、资源池管理 |
使用注意事项
使用条件变量时,等待条件的判断必须放在循环中,避免虚假唤醒问题,即线程被唤醒后条件可能仍未满足,需要重新检查。
信号量的初始值需要根据实际可用资源数量设置,P操作和V操作必须成对出现,否则可能导致计数器异常,引发线程永久阻塞或者资源访问冲突。
如果需要在多线程间传递简单的状态通知,且通知的场景是某个条件是否满足,优先选择条件变量;如果需要限制同时访问资源的线程数量,优先选择信号量。
条件变量信号量pthread_cond_tsem_t线程同步修改时间:2026-06-22 11:54:53