条件变量中使用互斥锁的必要性

简单介绍一下使用条件变量时,增加互斥锁的必要性。pthread_cond_wait的API如下

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

条件变量的用法就不具体介绍了,此处我们考虑的问题是增加mutex参数的必要性,先看看课本中的使用方法(只贴了主要的部分,包含生产者和消费者线程):

#include <stdio.h>
#include <pthread.h>
#define MAX 1000000000
pthread_mutex_t the_mutex;/*互斥锁*/
pthread_cond_t condc, condp;/*消费者和生产者条件变量*/
int buffer = 0;

void *producer(void *ptr)
{
  int i;
  
  for(i = 1; i <= MAX; i++)
  {
    pthread_mutex_lock(&the_mutex);
    while(buffer != 0)/*1*/
    {
      pthread_cond_wait(&condp, &the_mutex);/*2*/
    }
    buffer = i;
    pthread_cond_signal(&condc);
    pthread_mutex_unlock(&the_mutex);
  }
  pthread_exit(0);
}

void *consumer(void *ptr)
{
  int i;
  
  for(i = 1; i <= MAX; i++)
  {
    pthread_mutex_lock(&the_mutex);
    while(buffer == 0)/*3*/
    {
      pthread_cond_wait(&condc, &the_mutex);/*4*/
    }
    buffer = 0;
    pthread_cond_signal(&condp);
    pthread_mutex_unlock(&the_mutex);
  }
  pthread_exit(0);
}

线程在调用pthread_cond_wait并导致阻塞时会解锁互斥锁,解除阻塞并离开pthread_cond_wait时会加锁互斥锁。从代码中可以看到,该特性可以保证语句<1>和语句<3>中对buffer的访问和之后调用pthread_cond_wait导致的阻塞操作的原子性。

上面的解释可能不够直观,我们考虑一种错误的用法(去掉互斥锁),代码如下:

void *producer(void *ptr)
{
  int i;
  
  for(i = 1; i <= MAX; i++)
  {
    while(buffer != 0)/*1*/
    {
      pthread_cond_wait(&condp, NULL);/*2*/
    }
    buffer = i;
    pthread_cond_signal(&condc);
  }
  pthread_exit(0);
}

void *consumer(void *ptr)
{
  int i;
  
  for(i = 1; i <= MAX; i++)
  {
    while(buffer == 0)/*3*/
    {
      pthread_cond_wait(&condc, NULL);/*4*/
    }
    buffer = 0;
    pthread_cond_signal(&condp);
  }
  pthread_exit(0);
}

我们考虑一种可能的线程运行时序,如下所示(buffer初始值为0):

从图中可以看到在consumer线程对buffer进行访问并准备阻塞的时候,切换至producer线程运行,该线程更改了buffer的值,最终导致两个线程均被阻塞,发生死锁。

由此可以看出,在使用条件变量时,对需要判断的条件添加互斥锁的必要性。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。