一.信号量的基本概念
我们要想理解什么是信号量,就要先了解什么是对资源的整体使用和对资源的局部使用,我们来看:
在前面的章节中我们讲过ATM机的例子,现在我们在拿它来举例,ATM机这种小房间就是一个很好的对资源整体使用的例子,这个房间中一次只能进去一个人,别人要想进去就只能等里面的人出来,这个小房间的资源就只供一个人去使用。
而电影院想必我们每个人都去过,电影院就是一个很好的对资源进行局部使用的例子,在这间房间中,不只有我们自己可以看电影,座位上的每个人都可以观看电影,而我们通过座位就将电影院的这整个资源给划分为了一个个的局部资源。
通过上面的例子,我们对于资源的整体使用和局部使用有了一个的简单理解,我们今天的重点不在于对资源的整体使用,而在于对资源的局部使用。
我们来思考一个问题:我们在看电影之前或者说我们要看电影,我们首先要干什么?答案很明显,我们要买票,我们买票是为了什么呢?为了证明这个座位资源是我的,那么:对于这个座位资源,我们是买了票之后,这个座位就是我的,还是我坐到座位上这个资源才是我的呢?答案是我们买了票,这个座位就是我的,不管我今天去不去,这个座位就只能我坐,所以我们买票的本质就是:对资源的预定机制!!!
有了对上面的认识后,我们在来思考一个问题:作为一家电影院的老板,你最担心的是什么呢?
答案其实就两点:
1.多卖票。明明电影院就100个座位,却卖了110张票,那么就必然就有人没有座位,自然就会起冲突,没一个电影院的老板想看到这种现象。2.卖重复的票。一个座位按理说只能卖一张电影票,但却卖了10张,那么导致的后果就是这10个人争夺这一个座位,出现这种情况可谓是灾难性的。
对于这第二种卖重复的票我们暂且先不提,我们先着重说这第一种情况:我们在买票的时候,都会有一个售票系统,那么售票系统是怎么知道票是否卖完了呢?
答案就是这个售票系统的底层实现中,一定会有一个计数器,这个计数器记录了电影院有多少个座位,有多少个座位就卖多少张票,也就是这个计数器描述了电影院座位资源的多少。
而我们今天要讲的信号量,本质就是一个计数器,一个描述临界资源多少的计数器!!!
我们现在将视角转移到计算机中,我们可以通过信号量这个计数器,就可以保证:
1.资源不会出现多申请的情况,也就是上面的多卖票的情况。2.所有的进程或线程未来想进入临界区,去访问临界资源,都要先申请信号量,就如同上面我们要先买票一样。
既然是一个计数器,那么不可避免的我们就要对其进++和--的操作,--操作就表示我们申请了一份资源,++操作表示我们归还了一份资源。
而对于信号量的++和--操作,我们对其有更优雅的称呼:--操作我们称之为P操作,++操作我们称之为V操作。
讲到这里可能有人就问了:每个线程都要申请信号量的话,那么前提就是所有的线程都要看到信号量,也就是信号量本身就是共享资源,它保护了临界资源的安全,该怎么保护自己的安全呢?
答案就是我们改变信号量的方式就是通过PV操作,那换句话说,我们保证了PV操作的安全,也就保证了信号量的安全,那么该如何保证PV操作的安全呢?我们知道PV操作也就是--和++的操作,这种操作是可以被打断的,所以我们的做法就是让PV操作具有原子性,让--和++的过程不可被打断,这样就保证了PV操作的安全,也就保证了信号量的安全。
二.快速认识一下信号量接口
下面我们就来看看信号量的接口都有哪些:
那么第一个函数就是:sem_init,这个函数我们看名字就知道就是对信号量进行初始化。
而它的返回值很简单,成功就返回0,失败了就返回-1并且设置错误码。
下面我们来介绍它的三个参数:
1.sem_t *sem这个参数的作用很简单,我们既然想对信号量进行初始化,那得指明你要对那个信号量进行初始化吧,所以该参数的作用就是指明要初始化的对象。2.int pshared这个参数就有意思了,这个参数是一个int类型的参数,根据POSIX标准,这个函数的类型分为0和非0这两类。当该参数为0时,表示该信号量是进程内私有的,它只能被同一个进程内的不同线程使用,简单理解就是当前的信号量是在线程之间使用的。当该参数为非0时,表示该信号量是进程间共享的,也就是在进程间去使用。3.unsigned int value既然信号量是一个计数器,那我们总要给它设置一个起始值吧,所以该参数就是来设置信号量的初始值。
既然对一个信号量进行了初始化,那么我们不用的时候就要将其销毁,所以要用到的函数就是:sem_destroy。
它的返回值和上面的sem_init一样,都是成功了返回0,失败了就返回-1,并设置错误码。
那么下面就是要对信号量进行操作的函数了,第一个就是:sem_wait。
我们只看这个函数并不知道它是什么意思,而第二张图中的decrements就揭示了它的作用,没错,就是:P操作,也就是对信号量进行--操作。
那么与之相对应的就是:sem_post,上面的sem_wait函数是P操作,那么该函数就是就是:V操作,也就是对信号量进行++操作。
三.基于环形队列的生产者消费者模型
下面我们就要通过信号量来实现一个基于环形队列的生产者消费者模型,那么首先我们先来简单介绍一下环形队列。