深入理解条件变量(虚假唤醒)

深入条件变量

pthread_cond_wait()pthread_cond_signal()的伪实现

pthread_cond_wait(mutex,cond) {
	// 保存条件变量的值
	value = cond->value;
	// 解锁传入的已经锁住的互斥量
	pthread_mutex_unlock(mutex);  // 1
	// 这里和pthread_cond_signal/boardcast()有竞争
	pthread_mutex_lock(cond->mutex);  //2
	// 判断竞争谁赢了,我赢了,此线程休眠加入等待队列
	if (value == cond->value) {
		me->next_cond = cond->waiter;
		cond->waiter = me;
		pthread_mutex_unlock(cond->mutex);
		// 进行睡眠
		unable_to_run(me);
	} 
	// 竞争失败,那么就不加入等待队列了,相当于直接(唤醒)
	else 
		pthread_mutex_unlock(cond->mutex);
	// 重新锁住传入的互斥量
	pthread_mutex_lock(mutex);
}

pthread_cond_signal(cond) {
	// 竞争条件
	pthread_mutex_lock(cond->mutex); // 2
	cond->value++;
	// 判断等待队列上是否有线程
	if (cond->waiter) {
		sleeper = cond->waiter;
		cond->waiter = sleeper->next_cond;
		//唤醒等待队列上的线程,且移出等待队列
		able_to_run(sleeper);
	}
	pthread_mutex_unlock(cond->mutex);
} 

考虑2个线程, A 和 B

线程A执行pthread_cond_wait()后,线程B执行pthread_cond_signal(),此时2个线程同时竞争互斥量cond->mutex(语句2)

线程A在执行完语句1后就尝试进入等待队列,(此时mutex被释放,程序以为线程A已经被挂起,但实际上只是在尝试进入等待队列),

  1. 线程B获得锁cond->mutex,修改条件cond->value后,发现等待队列为空if (cond->waiter)不成立,则释放锁,线程A获得锁,发现条件被改变后又释放锁(因为没有加入到等待队列,又释放了锁,所以看起来像被唤醒了一样,(实际上根本没加入到等待队列中

所以被pthread_cond_wait()唤醒不一定要加入到等待队列中,具体看pthread_cond_signal()执行的时机

  1. 线程A获得锁cond->mutex,然后将自己加入到等待队列中,释放锁后挂起,然后线程B获得锁,if (cond->waiter)成立,将线程A从等待队列中移出(即唤醒)

3个线程 A B C

在上面情况的基础上,若线程C在线程A\B执行前已经在等待队列上,再次重复上面的情况,

  1. 线程A先获得锁,那么还是先加入到等待队列中,然后线程B在唤醒一个,线程C仍在等待队列中
  2. 线程B先获得锁,修改条件,并且把线程C唤醒了,释放锁后,线程A把自己"唤醒了",

所以说pthread_cond_signal()至少能唤醒一个等待该条件的线程

线程虚假唤醒

在线程B唤醒线程C释放锁mutex后,线程A(pthread_cond_wait)返回,但是线程C可能修改条件(其他情况也可能导致条件被修改),故造成线程被虚假唤醒,

解决方法

pthread_mutex_lock(&lock);
while (condvar == 0)
    // 唤醒之后再次检查条件
    pthread_cond_wait(&cond,&lock);
pthread_mutex_unlock(&lock);

你可能感兴趣的:(Linux学习)