第2课_线程同步之条件变量
热度🔥:21 免费课程
授课语音
线程同步之条件变量
1. 条件变量概述
条件变量(Condition Variable)是线程同步中的一种高级机制,用于解决线程间的通信问题,尤其是在某个线程需要等待某个条件满足时再继续执行的场景。它常常与互斥锁结合使用,以便在线程等待条件变化时,能够避免占用锁资源,释放锁直到条件满足。
条件变量本身不用于保护共享数据,而是通过与互斥锁的配合,提供一种等待和通知机制,帮助线程协调工作。
2. 条件变量的原理
条件变量允许线程等待某个特定的条件或事件发生。在等待期间,线程会释放与条件变量关联的互斥锁,直到被其他线程通知。通知的方式可以是“通知一个线程”或“通知所有线程”以唤醒等待线程继续执行。
2.1 主要操作
条件变量的操作主要有以下几种:
等待(wait):
- 线程调用
wait
操作时,它会释放关联的互斥锁并进入阻塞状态,直到被另一个线程通过notify
操作唤醒。 wait
通常是在一个循环中使用,以防止虚假唤醒(spurious wakeups)。
- 线程调用
通知(notify):
notify
操作会唤醒一个等待线程,但并不释放锁。- 适用于只需要唤醒一个等待线程的情况。
通知所有(notify_all):
notify_all
操作会唤醒所有等待的线程,通常用于当多个线程等待同一条件时,都需要被唤醒的情况。
2.2 条件变量与互斥锁的结合
- 互斥锁:条件变量通常与互斥锁一起使用。在使用条件变量时,线程首先获取互斥锁,检查条件是否满足。如果条件不满足,线程会调用
wait
函数,释放锁并进入等待状态;如果条件满足,线程会继续执行。 - 通知机制:当某个线程修改了共享资源并满足条件时,它会调用
notify
或notify_all
唤醒等待线程。
3. 条件变量的使用场景
条件变量通常用于以下几种场景:
- 生产者-消费者问题:生产者线程生产数据并放入缓冲区,消费者线程从缓冲区取数据。消费者在缓冲区为空时等待,生产者在缓冲区满时等待。
- 线程间依赖:多个线程之间存在某种依赖关系,某些线程必须等其他线程完成某些操作后才能继续执行。
- 事件驱动:线程在等待某个事件或条件发生后才能继续执行。
4. 条件变量的优缺点
4.1 优点
- 避免忙等待:与自旋锁相比,条件变量避免了繁忙的CPU资源消耗。当条件不满足时,线程会释放锁并进入阻塞状态,直到条件满足后唤醒。
- 适用于复杂的同步逻辑:条件变量非常适合需要等待某个特定条件的场景,能够有效协调多个线程的执行顺序。
4.2 缺点
- 需要与互斥锁配合使用:条件变量不能独立使用,它必须与互斥锁配合使用,这会增加编程的复杂性。
- 可能出现虚假唤醒:线程在等待条件时可能被虚假唤醒,因此通常需要在
wait
操作中使用循环来检查条件。 - 可能导致死锁:如果条件变量的使用不当,容易引发死锁,尤其是在多线程竞争锁的情况下。
5. 条件变量的实现(C语言示例)
在C语言中,条件变量通常与pthread_cond_t
类型配合使用,并与互斥锁pthread_mutex_t
一起使用。常见的操作包括pthread_cond_wait
、pthread_cond_signal
和pthread_cond_broadcast
。
5.1 示例:生产者-消费者模型
下面是一个使用条件变量的生产者-消费者模型的示例,生产者将数据放入缓冲区,消费者从缓冲区取数据:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 10
// 共享缓冲区
int buffer[BUFFER_SIZE];
int count = 0; // 缓冲区中的项目数量
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t cond_producer; // 生产者条件变量
pthread_cond_t cond_consumer; // 消费者条件变量
// 生产者线程函数
void* producer(void* arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex); // 获取互斥锁
// 如果缓冲区已满,等待消费者消费
while (count == BUFFER_SIZE) {
pthread_cond_wait(&cond_producer, &mutex);
}
// 生产数据并放入缓冲区
buffer[count++] = i;
printf("Produced: %d\n", i);
// 通知消费者可以消费
pthread_cond_signal(&cond_consumer);
pthread_mutex_unlock(&mutex); // 释放互斥锁
}
return NULL;
}
// 消费者线程函数
void* consumer(void* arg) {
for (int i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex); // 获取互斥锁
// 如果缓冲区为空,等待生产者生产
while (count == 0) {
pthread_cond_wait(&cond_consumer, &mutex);
}
// 消费数据
int item = buffer[--count];
printf("Consumed: %d\n", item);
// 通知生产者可以生产
pthread_cond_signal(&cond_producer);
pthread_mutex_unlock(&mutex); // 释放互斥锁
}
return NULL;
}
int main() {
pthread_t prod, cons;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond_producer, NULL);
pthread_cond_init(&cond_consumer, NULL);
// 创建生产者和消费者线程
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
// 等待线程完成
pthread_join(prod, NULL);
pthread_join(cons, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond_producer);
pthread_cond_destroy(&cond_consumer);
return 0;
}
5.2 代码解析
- 互斥锁:
pthread_mutex_t mutex
用于同步访问共享资源buffer
。 - 条件变量:
pthread_cond_t cond_producer
和pthread_cond_t cond_consumer
分别用于生产者和消费者线程的同步。 - 生产者逻辑:当缓冲区满时,生产者线程会调用
pthread_cond_wait
,阻塞自己并释放互斥锁,直到消费者消费了数据。 - 消费者逻辑:当缓冲区为空时,消费者线程会调用
pthread_cond_wait
,等待生产者生产数据。
6. 总结
- 条件变量是一种高级的线程同步机制,允许线程在等待某个条件时释放互斥锁,避免占用系统资源。
- 条件变量常用于需要线程等待某个条件或事件发生的场景,如生产者-消费者问题、线程间的依赖关系等。
- 条件变量需要与互斥锁配合使用,并且使用时需要注意虚假唤醒和死锁等问题。