APUE——pthread_cond_wait深度分析

参考链接1
源码1
惊群效应

1、pthread_cond_wait与signal函数

#include <pthread.h>

int pthread_cond_wait( pthread_cond_t *restrict cond,
             pthread_mutex_t *restrict mutex );

int pthread_cond_timedwait( pthread_cond_t *restrict cond,
                     pthread_mutex_t *restrict mutex,
                     const struct timespec *restrict timeout );

两者的返回值都是:若成功则返回0,否则返回错误编号
#include <pthread.h>

int pthread_cond_signal( pthread_cond_t *cond );

int pthread_cond_broadcast( pthread_cond_t *cond );

两者的返回值都是:若成功则返回0,否则返回错误编号
复制代码

传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。
pthread_cond_signal函数将唤醒等待该条件的某个线程
pthread_cond_broadcast函数将唤醒等待该条件的所有线程。
POSIX规范为了简化实现,允许pthread_cond_signal在实现的时候可以唤醒不止一个线程。
这里与我之前分析过的linux内核wait的实现方式不尽相同,内核wait的唤醒wakeup是唤醒最新加入wq上的_waiter,一次唤醒一个,如果唤醒会先再次加到等待队列,如果condition满足,则break,且从等待队列删除附上链接
这里要注意,mutex保护的条件并不是函数的参数cond,而是while循环判断的条件,while (workq == NULL),cond本身只是pthread_cond_t 变量,当pthread_cond_signal运行时,会唤醒等待在这个cond上的一个线程,如果在多处理器中可能唤醒多个线程产生惊群。

2、深入分析

如下代码是apue的例子

#include <pthread.h>
struct msg {
  struct msg *m_next;
  /* value...*/
};

struct msg* workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

void
process_msg() {
  struct msg* mp;
  for (;;) {
    pthread_mutex_lock(&qlock);
    while (workq == NULL) {
      pthread_cond_wait(&qread, &qlock);
    }
    mq = workq;
    workq = mp->m_next;
    pthread_mutex_unlock(&qlock);
    /* now process the message mp */
  }
}

void
enqueue_msg(struct msg* mp) {
    pthread_mutex_lock(&qlock);
    mp->m_next = workq;
    workq = mp;
    pthread_mutex_unlock(&qlock);
    /** 此时另外一个线程在signal之前,执行了process_msg,刚好把mp元素拿走*/
    pthread_cond_signal(&qready);
    /** 此时执行signal, 在pthread_cond_wait等待的线程被唤醒,
         但是mp元素已经被另外一个线程拿走,所以,workq还是NULL ,因此需要继续等待*/
}

pthread_cond_wait分析需要解决如下几个问题

  1. pthread_cond_wait()为什么必须要加while循环
  2. pthread_cond_signal之前的条件为什么要上锁
  3. pthread_cond_wait的优势是什么
    上面这几个问题是我在看apue的时候不解的,后续在查阅资料和自己分析之后,得出如下结论。

2.1 pthread_cond_wait()为什么必须要加while循环

pthread_cond_wait函数的实现是

  1. 加入等待队列
  2. 释放锁
  3. 调度
  4. 获得锁
    当pthread_cond_wait(cond,mutex)之后,被调度走,此时是被lock的状态,当signal(cond)运行,可能会产生惊群效应,因为posix为了实现简单,在多处理函数中,会将多个等待cond的线程从等待队列移除,进入就绪态。那么如果用if,则都会跳出循环,而用while会加锁后再次判断是不是需要的条件!==cond本身只是标志,实际的条件还是while循环的判断条件!==附上man链接pthread_cond_signal(3) - Linux man page
	thread1:
    if (0<a<10) {
      pthread_cond_wait(&qread, &qlock);
    }
    thread2:
    if (0<a<5) {
      pthread_cond_wait(&qread, &qlock);
    }
    pthread_mutex_lock(&qlock);
    a=6;
    pthread_mutex_unlock(&qlock);
    /** 此时另外一个线程在signal之前,执行了process_msg,刚好把mp元素拿走*/
    pthread_cond_signal(&qready);

这里线程1和2都wait在cond上,但是这个pthread_cond_signal本身只是为了唤醒线程1!

2.2 pthread_cond_signal之前的条件为什么要上锁

	thread1															thread2
	pthread_mutex_lock(&qlock);
    while (workq == NULL) {
														       	//pthread_mutex_lock(&qlock);
														  	workq == (void*)1;
														    //pthread_mutex_unlock(&qlock);
														    pthread_cond_signal(&qready);
      pthread_cond_wait(&qread, &qlock);
   
    }
    pthread_mutex_unlock(&qlock);

如上述代码所示,如果线程2没有对条件变量加锁,则线程2可能在线程1进入wait之前,发送信号,线程1还没有进入等待队列,所以信号丢失了!
这里实际可以理解为与读写锁类似,读写是互斥的,线程2是写,线程1为读

2.3 pthread_cond_wait的优势是什么

在以前分析线程池的时候,写过这种代码

thread1
        pthread_mutex_lock(&mut_num);
        while(num == 0)
        {
            pthread_mutex_unlock(&mut_num);
            sched_yield();  //schedule
            pthread_mutex_lock(&mut_num);
        }
        if(num>0)
        {
            i = num;
            num = 0;
            pthread_mutex_unlock(&mut_num);
            fprintf(stderr,"the num of thread %d,and the %d th  is %d\n",pthread_self(),(int)i);
        }
        else
        {
            pthread_mutex_unlock(&mut_num);//这里要注意,如果在临界区,跳转出临界区需要解锁再跳转
            break;
        }


这里如果起了4个线程,且num == 0时,这cpu在4个线程之间来回调度,不停的解锁上锁,非常耗费cpu资源!如果采用pthread_cond_wait,则把线程加入等待队列,cpu并不调度,降低cpu使用。同时,pthread_cond_wait可以保证加入等待队列和解锁之间是原子的

        pthread_mutex_lock(&mut_num);
        while(num == 0)
        {
			pthread_cond_wait(&cond, &qlock); //当num>0时,pthread_cond_signal(&cond)
        }
        ...
        pthread_mutex_unlock(&mut_num);

栗子

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t count_lock;
pthread_cond_t count_ready;
int count;

void *decrement_count(void *arg)
{

        pthread_mutex_lock(&count_lock);
        printf("decrement:waiting %d\n",pthread_self());
        /*等待满足条件,期间互斥量仍然可用*/
        //      while (count == 0)
        pthread_cond_wait(&count_ready, &count_lock);
        printf("decrement:count = %d,%d\n",  count,pthread_self());
        if (count == 0)
        {
                printf("exit count:%d\n",pthread_self());
                //break;
        }
        count = 0;
        pthread_mutex_unlock(&count_lock);
        
        pthread_exit(NULL);
}
void *increment_count(void *arg)
{

        pthread_mutex_lock(&count_lock);
        printf("increment:running\n");
        count = 1;
        /*通知线程条件已满足*/
        printf("increment:count = %d\n",  count);
        pthread_cond_signal(&count_ready);
        pthread_mutex_unlock(&count_lock);

        pthread_exit(NULL);
}

int main()
{
        pthread_t tid1,tid2,tid3;
        count=0;
        pthread_mutex_init(&count_lock, NULL);
        pthread_cond_init(&count_ready, NULL);

        pthread_create(&tid1, NULL, decrement_count, NULL);
        sleep(3);
        pthread_create(&tid3, NULL, decrement_count, NULL);
        sleep(3);
        pthread_create(&tid2, NULL, increment_count, NULL);
        /*等待decrement退出*/
        pthread_join(tid2, NULL);
        printf("decrement quit\n");
        pthread_join(tid3, NULL);
        pthread_join(tid1, NULL);
        return 0;
}
decrement:waiting 1482491648
decrement:waiting 1474098944
increment:running
increment:count = 1
decrement:count = 1,1482491648
decrement quit

此时没有惊群,只唤醒了一个线程

猜你喜欢

转载自blog.csdn.net/weixin_44537992/article/details/106308107