条件变量(为了实现多个线程间的同步操作)
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量使我们可以阻塞等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步 的一种机制,主要包括两个动作:
- 一个线程等待"条件变量的条件成立"而挂起;
- 另一个线程使 “条件成立”(给出条件成立信号)。
【原理】
条件的检测是在互斥锁的保护下进行的。线程在改变条件状态之前必须首先锁住互斥量。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量 可以被用来实现这两进程间的线程同步。
【条件变量的操作流程如下】
1. 初始化
2. 等待条件成立(这一步是要在加锁之后进行的)
3. 激活条件变量
4. 清除条件变量
#include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond,
pthread_condattr_t *cond_attr);
//或者
pthread_cond_tcond=PTHREAD_COND_INITIALIER
// 阻塞等待,并自动对mutex进行解锁,使得其它线程可以获得加锁的权利,当pthread_cond_wait返回的时候又自动给mutex加锁。
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
// 限时等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,
const timespec *abstime);
/*timewait()可以设置等待时间,如果条件仍未成立,返回ETIMEOUT(加锁保证只有一个线程wait);*/
// 销毁条件变量cond,并解除所有线程的阻塞
int pthread_cond_destroy(pthread_cond_t *cond);
// 至少唤醒一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒等待该条件的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
示例代码:
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include <unistd.h> int count = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//条件变量 void * add(void *arg) { while(1) { pthread_mutex_lock(&mutex); count++; if(count == 5) { pthread_cond_signal(&cond); } sleep(1); pthread_mutex_unlock(&mutex); sleep(1); } } void * printf_1(void *arg) { while(1) { while(count != 5) { pthread_cond_wait(&cond,&mutex); } printf("count = %d\n",count); count = 0; pthread_mutex_unlock(&mutex); sleep(1); } } int main() { pthread_t id1; pthread_t id2; pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); pthread_create(&id1,NULL,add,NULL); pthread_create(&id2,NULL,printf_1,NULL); pthread_join(id1,NULL); pthread_join(id2,NULL); }
虚假唤醒(spurious wakeup)
虚假唤醒(spurious wakeup)在采用条件等待时,我们使用的是:
while(条件不满足)
{
condition_wait(cond, mutex);
}
// 而不是:
If( 条件不满足 )
{
Condition_wait(cond,mutex);
}
这是因为可能会存在虚假唤醒”spurious wakeup”的情况。
也就是说,即使没有线程调用condition_signal, 原先调用condition_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就可能会导致后续的处理出现错误。
虚假唤醒在linux的多处理器系统中/在程序接收到信号时可能会发生。在Windows系统和JAVA虚拟机上也存在。在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造成的错误,因此程序的逻辑就变成了while循环的情况。
补充:C++11下实现
#include<iostream>
#include<condition_variable>
#include<mutex>
#include<thread>
using namespace std;
std::condition_variable cv;//条件变量
std::mutex mtx;//互斥锁
int isRead = 1;//可以理解为 通过此参数来决定当前应该执行哪个线程
const int n = 10;
void print_1()
{
std::unique_lock<std::mutex> lc(mtx);
int i = 0;
while (i < n)
{
while (isRead != 1)
{
cv.wait(lc);
//cout<<"print_1: "<<isRead<<endl;
}
cout << "A";
isRead = 2;
++i;
cv.notify_all();
}
}
void print_2()
{
std::unique_lock<std::mutex> lc(mtx);
int i = 0;
while (i < n)
{
while (isRead != 2)
{
cv.wait(lc);
//cout<<"print_2: "<<isRead<<endl;
}
cout << "B";
isRead = 3;
++i;
cv.notify_all();
}
}
void print_3()
{
std::unique_lock<std::mutex> lc(mtx);
int i = 0;
while (i < n)
{
while (isRead != 3)
{
cv.wait(lc);
//cout<<"print_3: "<<isRead<<endl;
}
cout << "C ";
isRead = 1;
++i;
cv.notify_all();
}
}
int main()
{
std::thread t1(print_1);
std::thread t2(print_2);
std::thread t3(print_3);
t1.join();
t2.join();
t3.join();
return 0;
}