互斥量,条件变量

注:本文主要参考<<现代操作系统>>2.3.6节

进程唤醒与睡眠一文中,针对多进程的生产者-消费者问题,我们提出了基于信号量的解决方案.该方案避免了进程在等待其要求的下一步执行条件时进入忙等待状态.我们使用了三个信号量,信号量mutex用于保证生产者进程与消费者进程不会同时访问缓冲区.信号量empty用于保证当缓冲区满是生产者被阻塞进入休眠状态,当缓冲区不满时其能够被再次唤醒.信号量full用于保证当缓冲区空时消费者被阻塞进入休眠状态,当缓冲区不空时其将被生产者进程唤醒.

信号量可以保存当前计数值,down操作对当前计数值减1,up操作对当前计数值加1,信号量也正是通过其当前计数值来判断是否应当阻塞调用进程.如果当前信号量取值为0,则down操作将阻塞当前调用进程.

我们能够在不利用信号量计数能力的条件下解决该问题呢?我们可以使用互斥量条件变量

基于互斥量与条件变量的解决方案
互斥量

互斥量主要用于确保对进程对临界区的互斥访问.它有两个状态,锁状态与解锁状态.对应的,对互斥量的两个可能操作为加锁操作与解锁操作.进程在准备进入临界区时,将尝试对当前互斥量加锁,如果当前互斥量为锁状态,则调用进程将被阻塞.如果当前互斥量为解锁状态,则进程将进入临界区,互斥量将被转化为锁状态.在进程准备离开缓冲区时,应当将当前互斥量解锁,并唤醒阻塞在该互斥量上的进程.如果多个进程阻塞在该互斥量上,则随机选择一个进程唤醒.

如何避免多进程(线程)因竞争条件引发的错误?一文中,我们提到,锁变量(即此处的互斥量)实现的重点在于确保对锁变量的加锁操作是原子操作,对锁变量的加锁操作包括两步:1.检查当前锁变量取值,2.加锁.因此必须通过适当的方案保证对加锁操作是原子性的.

条件变量

条件变量则用于在未达到下一步执行条件时阻塞调用进程.当下一条件满足时,需要通知当前条件变量取消对调用进程的阻塞.在多进程协作程序中,当前进程的执行通常可能需要满足一些特定条件,而该条件需要通过其他进程的执行来满足.因此如果当前进程在调用时被条件变量阻塞,则在其他进程满足该进程执行条件时,应当负责通知条件变量,取消对等待进程的阻塞.

在生产者-消费者问题中,生产者的执行需要保证当前缓冲区有空余空间,而这一条件需要消费者进程来提供.因此,如果消费者进程的执行提供了缓冲区有空余空间这一条件,则消费者进程应当通知条件变量,取消对生产者进程的阻塞.同样的,消费者进程的执行需要保证当前缓冲区不为空,而这一条件则需要生产者进程来提供.当生产者进程的执行使得当前缓冲区不为空时,其需要通知条件变量,取消对消费者进程的阻塞.条件变量的阻塞与唤醒通常命名为wait()signal()

互斥量与条件变量

互斥量与条件变量通常是同时使用的.进程首先使用互斥量确保当前对临界区的互斥访问,随后使用条件变量检查是否满足下一步执行条件.我们在唤醒与睡眠最后一段中提到,如果我们首先执行down(&mutex),再执行down(&empty),则可能使得生产者进程与消费者进程均阻塞,引发死锁.回到当前例子中,如果进程在对互斥量加锁后,被条件变量阻塞,那是否也会引发死锁呢?确实会.为了避免死锁,条件变量在阻塞当前调用进程前,必须对互斥量解锁,以使得其他进程可以执行.因此wait操作通常接受两个参数,一个是条件变量,一个是与其协作的互斥量.

关于条件变量,还有一点值得特别指出.不像信号量可以驻留在内存中,如果我们协作进程对某一条件变量调用signal(),然而此时并没有进程阻塞在该信号量上,则当前进程将被忽略.因此,程序员在编写程序时,一定要保证wait的调用在signal()之前.这个问题类似于我们在唤醒与睡眠中提到的,如果对进程的wakeup()操作调用发生在sleep()之前,则唤醒信号将被忽略.

使用pthread线程库实现多线程生产者与消费者

我们使用pthread多线程库解决多线程生产者与消费者问题.

#include <stdio.h>
#include <pthread.h>
#define MAX 10000000000 //需要生产的数量了
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) pthread_cond_wait(&condp, &the_mutex); 
		//使用大小为1的缓冲区,当缓冲区满时,调用wait操作阻塞当前进程,并解锁互斥量
		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) pthread_cond_wait(&condc, &the_mutex);
		buffer = 0; //从缓冲区中取出
		pthread_cond_signal(&condp);
		pthread_mutex_unlock(&the_lock);
	}
	pthread_exit(0);
}

int main(int argc, char ** argv){
	pthread_t pro, con; //定义线程变量
	pthread_mutex_init(&the_mutex, 0); //初始化互斥量
	pthread_cond_init(&condc, 0); //初始化条件变量
	pthread_cond_init(&condp, 0); 
	pthread_create(&con, 0, consumer, 0); //创建线程
	pthread_create(&pro, 0, producer, 0);
	pthread_join(pro, 0);
	pthread_join(con, 0);
	pthread_cond_destroy(&condc);
	pthread_cond_destroy(&condp);
	pthread_mutex_destroy(&the_mutex);
}

注意到,在条件变量的操作位于临界区当中,因此不会出现条件变量还未执行wait()操作便执行signal().例如,如果当前消费者判断buffer ==0 ,则将执行wait()操作阻塞消费者进程,则这一过程中,其不会被生产者影响,因为它位于临界区中.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值