操作系统-并发性:互斥&同步

参考文献
操作系统 —— 精髓与设计原理第八版(Operating systems: Internals and Design Principles, Eighth Edition , [美] Williams Stallings著,陈向群,陈渝 等译,P 136-146

信号量

  • 两个或多个进程通过见到那的信号进行合作,强迫一个进程在某个位置停止,直到它接收到一个特定的信号;
  • 为了发信号,使用变量 信号量s;
  • 原语(原子操作):(1)传送信号,semSignal(s),也称P操作;(2)接收信号,semWait(s),也称V操作;
  • 若相应的信号仍未发送,则阻塞进行,直到发送完为止.

信号量,一个值为整数的变量,整数值上定义3个操作:

  1. 一个信号量初始化成非负数
  2. semWait(s),信号量 -1 ,若值变成负数,则阻塞执行semWait的进程,否则继续执行
  3. semSignal(s),信号量 +1,若值小于等于0,则被semWait操作阻塞的进程解除阻塞

总过程:

  1. 开始时,信号量的值为0或整数:
    (1)若值为正数,则值等于发出semWait操作后,可立即执行的进程的数量
    (2)若值为0(原因为 初始化 or 已有进程在等待),则发出semWait操作的下一个进程会被阻塞(因为下一个进来后,先执行 -1,就变为了负值,则阻塞执行semWait的进程)
  2. 之后,每个后续的semWait操作都会使信号量的负值更大(上文说了,semWait操作先进行-1操作),如果为负值,则负值等于正在等待解除阻塞的进程的数量
  3. 在信号量为负值的情况下,每个semSignal操作都会将等待进程中的一个进程解除阻塞(上文说过,semSignal执行+1操作)

三个重要结论:

  1. 在对信号量-1前,不知道该进程是否会被阻塞;
  2. 当进程对一个信号量+1后,会唤醒另一个进程,两个进程继续并发运行。而在一个单处理器系统中,无法知道哪个进程先运行;
  3. 向信号量发出信号后,不需要知道是否有另一个进程正在等待,被解除阻塞的进程数要么没有,要么为1.

信号量

struct semaphore{
    int count;                      /信号量计数
    queueType queue;                
};
void semWait(semaphore s){
    s.count--;
    if(s.count<0){                  /临界区已满
        /*当前进程插入队列*/;        
        /*阻塞当前进程*/;   
    }
}
void semSignal(semaphore s){
    s.count++;
    if(s.count<=0){                 /semSignal释放了资源,因为s.count<0,所以唤醒阻塞的进程,但此情况下,下个在执行semWait的进程都会被阻塞
        /*把进程P从队列中移除*/;
        /*把进程P插入就绪队列*/;
    }
}

二元信号量

struct binary_semaphore{
    enum{zero,one} value;
    queueType queue;
};
void semWaitB(binary_semaphore s){
    if (s.value ==one ){            /s.value==1表示临界区有一个空位,==0表示有0个空位
        s.value=zero;
    }
    else{
        /把当前进程插入队列/;
        /阻塞当前进程/;
    } 
}
void semSignal(binary_semaphore s){
    if(s.queue is empty()){
        s.value = one;
    }
    else{
        /把进程P从等待队列中移除/;
        /把进程P插入就绪队列/;
    }
}

互斥锁

与二元信号量相关的一个概念“互斥锁”(mutex)。互斥锁是一个编程标志位,用来获取和释放一个对象。
当需要的数据不能被分析或处理,进而导致在系统中的其他地方不能同时执行时,互斥被设置为锁定(mutex=0),用于阻塞其他程序使用数据。
当数据不再需要或程序运行结束时,互斥被设定为非锁定。

与二元信号量区别:
互斥量加多(设为0)的进程和为互斥量解锁(设为1)的进程必须是同一个进程。二元信号量进程加锁,可能由另一个进程来解锁

互斥

在这里插入图片描述
上图为使用s信号量解决互斥问题的方法。每个进程进入临界区前执行semWait(s),即先执行 -1操作,若s<0,进程阻塞;若值 =0 ,进程立即进入临界区,之后s不再为正,因而其他进程都不能进入临界区

使用信号量的互斥

/*program mutualexclusion*/
const int n =/进程数/;
semaphore s=1;

void P(int 1){
	while(true){
		semWait(s);
		/临界区/;
		semSignal(s);
		/执行进程的其余部分/}
}

void main(){
	parbegin(P(1),P(2), ... ,P(n));
}

生产者/消费者问题(producer/consumer)

是并发处理中最常见的一类问题:
有一个或多个生产者生产某种类型的数据(记录,字符),并放置在缓冲区中,有一个消费者从缓冲区中取数据,每次取一项;系统保证避免对缓冲区的重复操作,即在任何时候只有一个主体(生产者或消费者)可访问缓冲区。
特殊情况需要保证:当缓存已满时,生产者不会继续向其中添数据;当缓存为空时,消费者不会从中移走数据。

缓冲区是无限的

用抽象的术语,可以定义如下的生产者和消费者函数:

producer:
while(true){
	/生产v/;
	b[in] = v;
	in++;
}

consumer:
while(true){
	while(in <= out){		/写入(索引)没有取出(索引)时,不做任何事
		/不做任何事/;
	}
	w = b[out];				/取数据
	out++/取数据索引+1
	/消费/;
}

在开始进行之前应该确保生产者已经生产( in > out )

使用二元信号量实现这个系统的而高压,不处理索引 in 和 out ,而使用整型变量n=in-out 简单地记录缓冲区中数据项的个数。信号量s用于实施互斥,信号量delay用于迫使消费者在缓冲区为空时等待(delay)
此方法里,生产者可以在任何时候自由地向缓冲区中增加数据项。它在添加数据前执行semWaitB(s),在之后执行semSignalB(s),以阻止消费者或任何其他生产者在添加操作过程中访问缓冲区。
但可能产生死锁

解决方法:引入一个辅助变量,可以在消费者的临界区中设置这个变量供以后使用

使用二元信号量解决无限缓冲区生产者/消费者问题的正确方法
/*program producerconsumer*/
	int n;
	binary semaphore s = 1 , delay = 0;
	void producer(){
		while(true){
			produce();					/执行程序
			semWaitB(s);				/信号量实施互斥,占用资源
			append();					/添加数据
			n++;						/添加后缓冲区数据个数+1
			if(n == 1){					/如果此时n=1 就释放delay信号 使消费者空时等待(刚写进去,还没热霍,防止出错)
				semSignal(delay);
			}	
			semSignalB(s);				/此时释放资源
		}
	}
	void consumer(){
		int m;							/局部变量
		semWaitB(delay);				/空时等待
		while(true){
			semWaitB(s);				/实施互斥
			take;						/取数据
			n--;						/缓冲区数据数-1
			m = n;						/局部变量=现存的缓冲区数据数
			semSignalB(s);				/释放资源
			consume();					/消费
			if(m == 0){					/如果m==0()缓冲区数据数为空,就开始等待(空时等待)
				semWaitB(delay);
			}
		}
	}
	 void main(){
	 	n=0;
	 	parbegin (producer, consumer);	/运行两个程序
	 }
使用信号量解决无限缓冲区生产者/消费者问题的方法

使用一般信号量(计数信号量),变量n为信号量,其值等于缓冲区中的项数

且操作semSignal(s)和senSignal(n)互换无影响,因为在cunsumer要这两个信号量都等到了才会继续下面的程序。
semWait(s)和semWait(n)不能互换,因为如果先semWait(s),如果此时 n=0 ,s又加了锁,consumer,无法消费,无法释放锁,producer因为consumer的s锁也无法写进去,即consumer等待producer的写入,producer等待consumer的释放使用权s,系统就会发生死锁

/*program producerconsumer*/
	semaphore n=0 , s=1;
	void producer(){
		while (true){
			produce();
			semWait(s);			/等待信号量s,得到使用权
			append();			/加入数据
			semSignal(s);		/释放使用权
			semSignal(n);		/信号量n=缓冲区中的项数,释放
		}
	}
	void consumer(){
		while(true){
			semWait(n);			/等n信号量
			semWait(s);			/等s信号量
			take();				/取数据
			semSignal(s);		/释放资源
			consume();			/消费
		}
	}
	void main(){
		parbegin (producer, consumer);
	}

增加约束:缓冲区是有限的,缓冲区被视为一个循环存储器,指针值为按缓冲区的大小取模,总是表示如下关系

被阻塞解除阻塞
生产者:在满缓冲区中插入消费者:移出一项
消费者:从空缓冲区中移出生产者:插入一项

生产者和消费者函数可写为以下形式:

producer:
while(true){
	/生产v/;
	while((in+1) % n == out){		/满时就不做事
		/不做任何事/;
	}
	b[in] = v;						/不满时就写进去
	in = (in + 1) % n;				/更新in的值
}

consumer:
while(true){
	while(in == (out+1) % n){	
		/不做任何事/;
	}
	w = b[out];
	out = (out + 1) % n;
	/消费w/;		
}
使用信号量解决有限缓冲区生产者/消费者问题的方法

使用一般信号量的解决方案,增加了信号量e来记录空闲空间的数量

/*program boundedbuffer*/
const int sizeofbuffer = /缓冲区大小/;
semophore s = 1, n = 0, e = sizeofbuffer;
 void producer(){
 	while(true){
 		produce();					/执行生产者程序
 		semWait(e);					/等待buffer中空闲空间的数量的信号量
 		semWait(s);					/锁住使用权
 		append();					/增加数据
 		semSignal(s);				/释放使用权
 		semSignal(n);				/发送buffer中有的数据量的值的信号量
 	}
 }
 void consumer(){
 	while(true){
 		semWait(n);					/等待缓冲区中数据的数量n的信号量
 		semWait(s);					/等待使用权
 		take();						/取走数据
 		semSignal(s);				/释放使用权
 		semSignal(e);				/发送现有的空闲空间的数量
 	}
 }
 void main(){
 	parbegin(producer,consumer);
}
信号量的实现

semWait 和 semSignal 操作需要为原子原语实现。问题的本质是互斥:任何时候只有一个进程可用semWait或semSignal操作控制一个信号量。可用硬件或软件的方案,但都增加额外开销。

对于单处理器系统,在semWait或semSignal操作期间可以禁用中断,因为这些操作的执行时间相对很短,因此这种方法是合理的

比较和交换指令(看不懂,看下面中断的看懂了也一样):

semWait(s){
	while(compare and swap(s.flag,0,1) == 1){
		/不做任何事/;
	}
	if (s.count < 0) {
		/该进程进入s.queue队列/;
		/阻塞该进程(还须将s.flag置0/;
	}
	s.flag = 0;
}

中断:

semWait(s){
	禁用中断;
	s.count--;						/先执行-1操作
	if(s.count < 0){				/ 执行-1操作后小于0就进队列阻塞
		/该进程进入s.queue队列/;
		/阻塞该进程,并允许中断/;
	}
	else{							/进入临界区,不能让其他的进程中断
		/允许中断/;					/但是可以自己要使用其他东西的时候就中断
	}
}
semSignal(s){
	禁用中断;
	s.count++;
	if (s.count <= 0){
		/从s.queue队列中移出进程P/;
		/进程P进入就绪队列/;
	}
	允许中断;						/原子操作执行完毕,可以中断了
}
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
PV操作是指操作系统中用于进程通信和同步的一种机制。信号量(Semaphore)是一种计数器,用于控制对共享资源的访问。它具有两个基本操作:PV操作。PV操作也被分为两个部分:P操作和V操作。 P操作(Wait操作)用于申请资源。当进程需要使用一个共享资源时,它会尝试对信号量进行P操作。如果信号量的值大于0,表示资源可用,进程可以继续执行并将信号量的值减少1。如果信号量的值为0,表示资源已经被其他进程占用,此时进程会被阻塞,等待资源释放。 V操作(Signal操作)用于释放资源。当进程使用完一个共享资源后,它会对信号量进行V操作。V操作会将信号量的值加1,表示资源已经被释放。如果有其他进程在等待该资源,那么其中一个进程将被唤醒继续执行。 通过信号量PV操作,可以实现进程的互斥同步。当多个进程同时访问一个共享资源时,信号量可以控制对资源的访问顺序,避免数据的竞争和冲突。例如,在多个进程并发读写文件时,可以使用信号量来保证同一时间只有一个进程对文件进行读写操作。 在实际应用中,操作系统的信号量用于解决进程之间的竞争和资源分配的问题。通过合理的使用信号量,可以实现进程的并发执行和协同工作。在编程中,我们可以使用各种编程语言提供的信号量库函数或系统调用来实现信号量的操作,如C语言中的sem_init()、sem_wait()、sem_post()等函数。 总之,信号量PV类题目是计算机操作系统中常见的题型,需要理解PV操作的含义和作用,以及如何使用信号量来解决进程之间的同步和通信问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值