pthread_mutex_t 和 pthread_cond_t 配合使用的简要分析

22 篇文章 3 订阅

1.原理

假设有两个线程同时访问一个全局变量 n,这个全局变量的初始值等于0。

Int  n = 0 ;

         消费者线程 A 进入临界区,访问 n,A 必须等到 n 大于 0 才能接着往下执行,如果 n== 0,那么 A 将一直等待。

         还有一个生产者线程 B,B 进入临界区,修改 n 的值,使得 n >0,当 n > 0 时,B 通知等待 n > 0 的消费者线程A。A 被 B 通知之后就可以接着往下执行了。

 

pthread_mutex_t 和 pthread_cond_t 配合使用的简要分析

 

         以上情况造成死锁:

         当 A 进入临界区时,其他线程不能进入临界区,意味着 B 没有机会去修改 n, n 的值一直为 0,不满足A 继续执行的条件(n > 0),A 只能一直等待。

         消费者进程拿到互斥锁 --> 进入临界区 --> 发现共享资源 n 不满足继续执行的条件(n> 0) --> 等待 n > 0

         消费者进程占有互斥锁 --> 生产者进程无法进入临界区 --> 无法修改 n 的值 --> 生产者等待消费者释放互斥锁

         解决死锁的方案就是采用条件变量。

         通常情况下,对共享资源(比如 n)保护要用到锁操作,当一个进程进入临界区时会拿到互斥锁(lock 操作),然后其他进程拿不到互斥锁,也就无法进入临界区,因此当进程进入临界区,发现共享资源不满足继续向下执行的条件(n > 0)时,就应该释放锁,让其他进程修改共享资源,以满足自己所需的执行条件。

消费者进入临界区 --> 共享变量不满足继续向下执行的条件 --> 消费者等待在条件变量 --> 释放互斥锁 --> 生产者进入临界区 --> 修改条件变量 --> 生产者通知消费者:现在有多的资源了,快来使用 --> 消费者再次拿互斥锁 --> 消费资源 --> 释放互斥锁。如果有多个消费者进程等待在条件变量上,就可以形成等待队列。

生产者和消费者模型中互斥锁和条件变量的使用流程图如下,其中蓝色代表消费者的执行流,红色是生产者的执行流。

pthread_mutex_t 和 pthread_cond_t 配合使用的简要分析

 

  2.使用方法

         条件变量的使用主要有以下五个函数:

/* 初始化一个条件变量 */ int pthread_cond_init (pthread_cond_t* cond, pthread_condattr_t *cond_attr); /* 销毁一个条件变量 */ int pthread_cond_destroy(pthread_cond_t* 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);

消费者等待条件的伪代码:

pthread_mutex_lock(&mutex); // 拿到互斥锁,进入临界区 while( 条件为假) pthread_cond_wait(cond, mutex); // 令进程等待在条件变量上 修改条件 pthread_mutex_unlock(&mutex); // 释放互斥锁

生产者通知消费者的伪代码:

pthread_mutex_lock(&mutex); // 拿到互斥锁,进入临界区 设置条件为真 pthread_cond_signal(cond); // 通知等待在条件变量上的消费者 pthread_mutex_unlock(&mutex); // 释放互斥锁

以下是示例程序,演示了互斥锁和条件变量配合使用方法,由于是在Linux下写的程序,所以注释全是英文的。

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

#define CONSUMERS_COUNT 2
#define PRODUCERS_COUNT 1 
pthread_mutex_t g_mutex ; 
pthread_cond_t g_cond ; 
pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT] ; 
int share_variable = 0 ;
// this is the share variable, shared by consumer and producer 
void* consumer( void* arg ) 
{ 
        int num = (int)arg ; 
        while ( 1 ) { 
                /******* critical section begin *******/ 
                pthread_mutex_lock( &g_mutex ) ; // if share_variable == 0, means consumer shell stop here 
                while ( share_variable == 0 ) {
                        printf( "consumer %d begin wait a condition...\n", num ) ; 
                        // put a thread blocked ont a condition variable( here is g_cond), // and unlock the mutex( here is g_mutex ) 
                        pthread_cond_wait( &g_cond, &g_mutex ) ; 
                } // here means n != 0 and consumer can goes on // consumer consumed shared variable, so the number of shared variable shell minus 
                printf( "consumer %d end wait a condition...\n", num ) ; 
                printf( "consumer %d begin consume product\n", num ) ; 
                -- share_variable ; 
                pthread_mutex_unlock( &g_mutex ) ; /******** critial section end *********/ 
                sleep( 1 ) ; 
        } 
        return NULL ; 
} 
void* producer( void* arg ) 
{ 
        int num = (int)arg ; 
        while ( 1 ) { 
                /******* critical section begin *******/ 
                pthread_mutex_lock( &g_mutex ) ; // produce a shared variable 
                printf( "producer %d begin produce product...\n", num ) ; 
                ++ share_variable ; 
                printf( "producer %d end produce product...\n", num ) ; 
                // unblock threads blocked on a condition variable( here is g_cond ) 
                pthread_cond_signal( &g_cond ) ; 
                printf( "producer %d notified consumer by condition variable...\n", num ) ; 
                pthread_mutex_unlock( &g_mutex ) ; /******** critial section end *********/ 
                sleep( 5 ) ; 
        } 
        return 1 ; 
} 
int main( void ) 
{ 
        // initiate mutex 
        pthread_mutex_init( &g_mutex, NULL ) ; 
        // initiate condition 
        pthread_cond_init( &g_cond, NULL ) ; 
        // initiate consumer threads 
        for ( int i = 0; i < CONSUMERS_COUNT; ++ i ) { 
                pthread_create( &g_thread[i], NULL, consumer, (void*)i ) ; 
        } 
        sleep( 1 ) ; 
        // initiate producer threads 
        for ( int i = 0; i < PRODUCERS_COUNT; ++ i ) { 
                pthread_create( &g_thread[i], NULL, producer, (void*)i ) ; 
        } 
        for ( int i = 0; i < CONSUMERS_COUNT + PRODUCERS_COUNT; ++ i ) {
                pthread_join( g_thread[i], NULL ) ; 
        } 
        pthread_mutex_destroy( &g_mutex ) ; 
        pthread_cond_destroy( &g_cond ) ; 
}

编译:cc test.c -o test -lpthread -std=c11

运行程序:

pthread_mutex_t 和 pthread_cond_t 配合使用的简要分析

 

1.      第一个框,消费者 1 和0 发现share_variable == 0,于是先后等待在条件变量上;

2.      第二个框,生产者 0 开始生产共享变量,即 ++ share_variable,然后通知等待在条件变量上的消费者;

3.      第三个框,消费者 1 被生产者唤醒,开始消费共享变量,即– share_variable;

4.      第四个框,生产者 0 继续生产共享变量,++ share_variable,然后通知等待在条件变量上的消费者;

5.      第五个框,消费者 0 被唤醒,开始消费共享变量,即– share_variable;

以此类推,以上描述简化了拿锁和释放锁的过程,可以结合上面的流程图来理解代码。

问题答疑:

为什么pthread_cond_wait需要加锁??

pthread_cond_wait中的mutex用于保护条件变量,调用这个函数进行等待条件的发生时,mutex会被自动释放,以供其它线程(生产者)改变条件,pthread_cond_wait中的两个步骤必须是原子性的(atomically,万恶的APUE中文版把这个单词翻译成了『自动』,误人子弟啊),也就是说必须把两个步骤捆绑到一起:

  • 把调用线程放到条件等待队列上
  • 释放mutex

不然呢,如果不是原子性的,上面的两个步骤中间就可能插入其它操作。比如,如果先释放mutex,这时候生产者线程向队列中添加数据,然后signal,之后消费者线程才去『把调用线程放到等待队列上』,signal信号就这样被丢失了。

如果先把调用线程放到条件等待队列上,这时候另外一个线程发送了pthread_cond_signal(我们知道这个函数的调用是不需要mutex的),然后调用线程立即获取mutex,两次获取mutex会产生deadlock.

消费者线程中判断条件为什么要放在while中??

我们把while换成if可不可以呢?

答案是不可以,一个生产者可能对应着多个消费者,生产者向队列中插入一条数据之后发出signal,然后各个消费者线程的pthread_cond_wait获取mutex后返回,当然,这里只有一个线程获取到了mutex,然后进行处理,其它线程会pending在这里,处理线程处理完毕之后释放mutex,刚才等待的线程中有一个获取mutex,如果这里用if,就会在当前队列为空的状态下继续往下处理,这显然是不合理的。

signal到底是放在unlock之前还是之后??

如果先unlock,再signal,如果这时候有一个消费者线程恰好获取mutex,然后进入条件判断,这里就会判断成功,从而跳过pthread_cond_wait,下面的signal就会不起作用;另外一种情况,一个优先级更低的不需要条件判断的线程正好也需要这个mutex,这时候就会转去执行这个优先级低的线程,就违背了设计的初衷。

如果把signal放在unlock之前,消费者线程会被唤醒,获取mutex发现获取不到,就又去sleep了。浪费了资源.但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
所以在Linux中推荐使用这种模式。

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值