1. 信号量机制

信号量(semaphore)也被成为信号灯。是在多线程环境下用来保证关键代码段不被并发调用,在进入一个关键代码段之前每个线程必须要获取一个信号量,一旦关键代码段执行完成了,拥有信号量的线程必须释放信号量。另外想进入关键代码段的线程必须等待直到拥有信号量的线程释放了信号量。为了完成以上这个过程,就需要创建一个信号量,并且将获取信号量和释放信号量的操作分别放置在关键代码段的开始和结束处。

  • 例:多个线程都想执行 读取一个文件内容的操作,但同一时刻该操作只能满足一个线程,为了保护读取文件内容的有序操作,就可以使用信号量来保护这段关键代码。
  • 备注:**信号量可以实现线程的同步和互斥,但大多数情况下用于线程间的同步

    2 .PV原子操作的原理

  • 前面所讲到的信号量也就是操作系统所用到的PV 原子操作,它广泛用于进程或线程之间的同步和互斥,信号量本身是一个非负整数计数器,而PV 原子操作就是对这个整数计数器信号量(sem)的操作

  • P 操作:(减)
    • 一次P操作使信sem 减 1, 如果 sem>=0 进程或者线程具有公共资源的访问权;
  • V 操作:(加)
    • 一次V操作使信sem 加 1, 如果 sem<0 进程或者线程就将阻塞,直到 sem>=0为止;
  • 当一个进程或者线程需要访问关键代码段(临界区)时,它必须先执行 P操作原语,使信号量S减1,当完成对临界区访问后,执行V操作来释放占用的临界区,若信号量的初值为1,当一个进程或者线程执行P操作后,S变成0,可以访问临界区,在该进程或者线程没有执行V操作之前,其他进程或线程也想访问临界区,也必须执行P操作,从而S 就变成-1,因此第二个进程或线程就会被阻塞,直到第一个退出临界区,这时S变为0,从而唤醒第二个进程或线程等待系统调度后再进入临界区,当第二个进程或线程访问完后,执行V操作,如果没有其他进程或线程申请进入,S变成初值1。
  • 也就是说:

    • P(S) 操作表示进程(或线程) 申请进入临界区,而V(S) 操作表示进程(或线程) 退出临界区;
    • 信号量用于互斥问题的解决:
      • 1)用于互斥,往往只需要一个信号量;并且信号量初值设为资源数;
      • 2)每个线程在共享资源访问前都是对信号量进行P操作,访问完毕后V操作;

        3 .信号量分别用于互斥和同步的流程

  • 当信号量用于互斥的情况:

    • 几个进程(或线程)往往只设置一个信号量,且将信号量初值设为1。
      • 线程同步-Semaphore - 图1
  • 当信号量用于同步的情况:
    • 几个进程(或线程)往往设置多个信号量,且将其中一个信号量初值设为1(通常设置为资源总数)。其他为0
      • 线程同步-Semaphore - 图2
  • 典型应用——哲学家就餐问题
    • 6个哲学家,5双筷子,怎么进餐?
    • 生产者,消费者问题。

      4 .信号量相关操作函数(POSIX标准)

      1.初始化信号量
      sem-init(&sem,0,1)
      image.png
      2.P操作
      sem_wait(&sem)
      image.png
      3.V操作
      sem_post(&sem)
      image.png
      4.回收信号量
      sem_destroy(&sem)

image.png

image.png


  • 函数:sem_getvalue
  • 头文件:#include < semaphore.h>
  • 函数原型:int sem_getvalue(sem_t sem, int semval);
  • 功能:获取信号量值得。
  • 参数:
    • 1)sem:指向信号量结构的一个指针。
    • 2)semval:获取的信号量的值放到这个变量地址里,如果有一个或者多个进程或者线程当前正在使用sem_wait等待信号量,semval可能会返回两种结果,要么,返回0,要么返回一个负值,它的绝对值等于当前正在使用P操作的阻塞的进程或者线程数的总和。注意:信号量的值可能在sem_getvalue返回时,已经被更改。
  • 返回值;
    • 成功:返回0;
    • 失败:返回-1。