信号量

信号量

简介

信号量(semaphore)由(1972年的图灵奖获得者,实现了ALGOL60的编译器、提出了图论中最短路径的Dijkstra算法的)荷兰计算机科学家E. W. Dijkstra提出(1965),是解决并发进程问题的第一个重要进展,需要OS支持。
信号量可以用于进程之间的互斥与同步,还可以用于进程间通信(IPC),这里主要讲进程之间的互斥与同步。
信号量实际上是一个数字,这个数字表示了可用资源的数目。
信号量可以用于用于多个进程,不仅仅是两个进程

信号量的操作

  1. 初始化,信号量可以初始化为非负数,表示可用资源数目
  2. P操作/semWait操作 信号量的值-1,若信号量值变为负数,则执行P操作的进程变为阻塞态,进入阻塞队列,实际意义为申请一个资源,如果资源数为负数,则把进程自己放进阻塞队列–让权等待
  3. V操作/semSignal操作,信号量的值+1,如果信号量的值不是正数,则唤醒(wake up)一个因执行P操作而阻塞的进程.为什么要判断信号量不是正数呢,假设如果是正数,那么就表示V操作的数目比P操作的数目要多(一个P操作-1,一个V操作+1),这表示其实阻塞队列里面其实已经没有在等待的进程了,所以此时不需要唤醒操作。当信号量的值不是正数时,它的值的绝对值等于阻塞队列中的进程数目,V操作的实际意义是释放一个资源,如果等待队列不为空,则唤醒一个队列中的进程获取资源

信号量的PV操作都必须是原子操作

信号量的伪代码描述

  • 信号量的结构
struct semaphore{ //定义结构
    int count;
    queueType *queue;
 } s;
  • 初始化
// 初始化
s.count = nr;
s.queue =malloac(m*sizeof(queueType));
  • P操作
void  P(semaphore s) { // P操作
    s.count--;
    if (s.count<0)  
       Block(CurruntProcess, s.queue);
 }
  • V操作
void  V(semaphore s) {// V操作
    s.count++;
    if (s.count<=0)  
       WakeUp(s.queue);
 }

PV操作的原子性实现

PV操作的原子性其实是对于其他PV操作而言的,即一个P或V操作不能被其他的PV操作给打断,即需要实现PV操作的互斥。
那么我们可以把PV操作的代码当成临界区,保证只有一个进程能访问临界区即可。
实现对临界区资源的互斥访问的方法有很多种,比如Peterson算法,禁用中断,加锁等等等
下面实现一种用testset指令实现的PV互斥
testset指令详细可以参考另一篇文章
https://blog.csdn.net/qq_36267931/article/details/103196271

  • 信号量的结构
struct semaphore{
    int flag; //新加一个flag用于实现互斥
    int count;
    queueType *queue;
 } s;
  • 初始化
// 初始化
s.flag=0;
s.count = nr;
s.queue =malloac(m*sizeof(queueType));
  • P操作
void  P(semaphore s) {
   while (testset(s.flag));
   s.count--;
   if (s.count<0)  
        Block(CurruntProcess, s.queue);
   s.flag=0;
 }
  • V操作
void  V(semaphore s) {
   while (testset(s.flag)); 
   s.count++;
   if (s.count<=0)  
            WakeUp(s.queue);
   s.flag=0;
}

信号量的分类

  1. 按照信号量的值来划分
    • 二元信号量 值只能是0或1,因为值不能为负数,所以V操作中判断队列是否为空需要实现其他的方法
    • 非二元信号量/一般信号量/计数信号量,就是一般意义上的信号量
  2. 按等待队列的出对顺序划分
    • 强信号量
      采用FIFO,保证不会饥饿
    • 弱信号量
      没有规定出队顺序,不能保证不会饥饿

信号量实现互斥

semaphore s=1;
void P(int i){
    while (true){
        P(S);
        //临界区
        V(S);
    }
}

信号量初始化为1,表示一个时刻只能有最多一个进程访问临界区资源,进而实现互斥

信号量实现同步

经典的生产者消费者问题
描述:若干进程通过无限的共享缓冲区交换数据,一组生产者进程不断写入,一组消费者进程不断读出,任何时刻只能有一个进程对缓冲区进行操作

semaphore n=0; /*缓冲区中的产品数*/
semaphore s=1; /*互斥*/


void producer()  {
  while (true)  {
    produce();
    P(s);
    append();
    V(s);
    V(n);
  }
} 

void consumer()  {
  while (true)  {
    P(n);
    P(s);
    take();
    V(s);
    consume();
  }
}   

void main()  {
   parbegin(producer, consumer);
}

n是缓冲区中的产品数,producer添加产品到缓冲区后执行V(n)让缓冲区中产品数+1,consumer执行P(n)让缓冲区产品数-1,如果没有产品则等待,s是保证进程互斥的信号量,注意consumer中的两个P操作顺序不能变,否则会造成死锁

其实生产者消费者问题还有另一个版本,即缓冲区是有限的为N,下面是有限缓冲区的生产者消费者问题的解决方案

const int sizebuffer=N
semaphore n=0; /*产品数*/
semaphore s=1;  /*互斥*/
semaphore e=N; /*空闲数*/

void producer()  {
  while (true)  {
    produce();
    P(e);
    P(s);
    append();
    V(s);
    V(n);
  }
}   


void consumer()  {
  while (true)  {
    P(n);
    P(s);
    take();
    V(s);
    V(e);
    consume();
  }
}   
void main()  {
   parbegin(producer, consumer);
}


信号量的优缺点

优点:简单,表达能力强,用PV操作可以解决多种类型的同步/互斥问题
缺点:不够安全,PV操作使用不当容易产生死锁,遇到复杂同步互斥问题实现复杂

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值