在Linux环境下,条件变量(Condition Variables)是一种线程同步机制,允许线程在某个条件未满足时进入等待状态,并在其他线程修改共享资源或条件后通知它们。
条件变量和互斥锁通常一起使用,以保证对共享资源的安全访问。
通过条件变量,线程可以避免忙等待(busy-waiting),从而提高效率。
条件变量使得线程可以通过以下方式同步:
等待某个条件满足:当某个线程在等待某个条件时,它可以进入阻塞状态,并释放持有的互斥锁,以允许其他线程操作共享资源。
通知条件满足:当其他线程改变了共享资源的状态,且满足了等待线程的条件,它可以通过发送信号(signal)来通知那些正在等待的线程,使它们被唤醒并继续执行。
条件变量通常与互斥锁结合使用,因为在检查或修改某些共享资源时,需要保护这些资源的并发访问,防止竞争条件。
互斥锁负责保护共享资源,条件变量负责在线程间传递状态信息。
具体步骤如下:
线程通过互斥锁访问共享资源。
当条件未满足时,线程通过条件变量进入等待,并释放互斥锁,允许其他线程继续操作资源。
其他线程修改共享资源并发出条件满足的信号,通知条件变量唤醒等待线程。
被唤醒的线程重新获得互斥锁并检查条件是否满足,如果满足则继续执行,否则继续等待。
条件变量的使用步骤:
初始化条件变量和互斥锁。
在线程中使用互斥锁对共享资源进行保护。
如果条件未满足,调用pthread_cond_wait()使线程进入阻塞状态,同时释放互斥锁,等待其他线程通知条件满足。
其他线程修改共享资源后,调用pthread_cond_signal()或pthread_cond_broadcast()通知等待线程。
被唤醒的线程重新获得互斥锁并继续检查条件。
1
条件变量的初始化和销毁
条件变量使用pthread_cond_t数据类型来表示,和互斥锁类似,条件变量在使用前必须初始化。
可以通过两种方式进行初始化:
1、使用宏PTHREAD_COND_INITIALIZER,类似于互斥锁的静态初始化方式:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2、使用pthread_cond_init()函数动态初始化条件变量:
pthread_cond_init(&cond, NULL); // attr 为 NULL 时使用默认属性
参数:
cond: 指向待初始化的条件变量对象,类型为 pthread_cond_t*。
attr: 条件变量的属性,可以为 NULL 表示使用默认属性。
返回值:
成功返回 0;
失败返回非零错误码,如 EINVAL(无效属性)。
当不再使用条件变量时,应使用pthread_cond_destroy()进行销毁,以释放系统资源,销毁前需要确保没有线程在等待该条件变量。
pthread_cond_destroy(&cond);
参数:
cond: 指向待销毁的条件变量对象。
返回值:
成功返回 0;
失败返回非零错误码,如 EBUSY(有线程在等待该条件变量)。
注意事项:
只能销毁已经初始化的条件变量。
条件变量销毁时,不能有线程在等待它,否则将导致未定义行为。
2
发送信号和等待条件变量
条件变量的核心功能就是发送信号和等待条件。
在多线程程序中,线程通过pthread_cond_wait()等待条件,而其他线程通过pthread_cond_signal()或pthread_cond_broadcast()发出信号。
pthread_cond_wait():使线程等待条件满足,并在等待期间释放互斥锁。该函数会阻塞调用线程,直到另一个线程调用 pthread_cond_signal() 或 pthread_cond_broadcast() 通知该条件变量。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
参数:
cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。
mutex: 互斥锁指针,指向 pthread_mutex_t 类型的互斥锁。在线程进入等待状态时,pthread_cond_wait 会自动释放该互斥锁,并在线程被唤醒时重新加锁。
返回值:
成功返回 0;
失败返回非零错误码。
注意事项:
在调用 pthread_cond_wait() 之前,必须先锁住互斥锁,以避免条件检查和等待之间的竞争。
线程被唤醒时,会重新锁住传入的互斥锁。
pthread_cond_signal():用于通知至少一个等待该条件变量的线程,使其从 pthread_cond_wait() 的阻塞状态中唤醒。如果没有线程在等待条件变量,该信号会被丢弃。
int pthread_cond_signal(pthread_cond_t *cond);
参数:
cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。
返回值:
成功返回 0;
失败返回非零错误码。
注意事项:
该函数只能唤醒一个等待的线程。
如果没有线程等待,信号不会累积,因此没有线程在等待时,调用该函数不会有任何效果。
pthread_cond_broadcast():用于唤醒所有等待该条件变量的线程。适用于需要唤醒多个线程的场景。
int pthread_cond_broadcast(pthread_cond_t *cond);
参数:
cond: 条件变量指针,指向 pthread_cond_t 类型的条件变量。
返回值:
成功返回 0;
失败返回非零错误码。
假设有多个消费者线程在等待产品,如果使用pthread_cond_signal(),只有一个线程会被唤醒。
如果我们希望所有消费者都被唤醒,则使用pthread_cond_broadcast()。
但一般来说,pthread_cond_signal()更高效,除非确实需要所有线程都被唤醒。
pthread_cond_signal(&cond); // 唤醒一个线程
pthread_cond_broadcast(&cond); // 唤醒所有等待的线程
3
条件变量中的判断条件
在使用条件变量时,通常涉及某个共享变量(如上例中的buffer)。
在检测条件时,必须使用while循环而非if语句。这是因为:
虚假唤醒:线程可能被意外唤醒,但条件仍不满足。用while循环可以确保条件再次检查,而不是立即继续执行。
竞争条件:多个线程可能同时被唤醒,但只有一个线程会成功获取锁并修改条件状态。其他线程必须再次等待。
这样设计能确保线程在条件不满足时不会继续执行,从而避免竞争条件带来的问题。
pthread_mutex_lock(&mutex);
while (buffer == 0) {
pthread_cond_wait(&cond, &mutex);
}
以下示例展示了生产者-消费者模型,其中生产者线程和消费者线程通过条件变量进行同步:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer = 0; // 缓冲区,0表示空,1表示有产品
void *producer(void *arg) {
pthread_mutex_lock(&mutex); // 加锁
while (buffer == 1) {
pthread_cond_wait(&cond, &mutex); // 缓冲区满,等待
}
buffer = 1; // 生产产品
printf("Produced an item\n");
pthread_cond_signal(&cond); // 通知消费者
pthread_mutex_unlock(&mutex); // 解锁
return NULL;
}
void *consumer(void *arg) {
pthread_mutex_lock(&mutex); // 加锁
while (buffer == 0) {
pthread_cond_wait(&cond, &mutex); // 缓冲区空,等待
}
buffer = 0; // 消费产品
printf("Consumed an item\n");
pthread_cond_signal(&cond); // 通知生产者
pthread_mutex_unlock(&mutex); // 解锁
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);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
Linux中的条件变量是线程同步的强大工具,允许线程等待特定条件满足后再执行操作,避免了无效的忙等待。
通过与互斥锁协作,条件变量可以有效地协调线程之间对共享资源的访问,保证并发环境下的安全性和效率。
条件变量与互斥锁结合使用:条件变量用于等待和通知条件变化,互斥锁则用于保护共享资源的访问。
条件变量不保存状态:如果没有线程在等待条件变量,信号会丢失。
pthread_cond_wait()应放在循环中:因为多个线程可能竞争资源,因此从 pthread_cond_wait() 返回后应重新检查条件。
选择合适的通知方式:pthread_cond_signal() 唤醒一个等待线程,pthread_cond_broadcast() 唤醒所有等待线程。