一、引入信号量
共享资源作为整体使用时,若需将资源分开局部使用,例如电影院座位分配,需考虑两个问题:放过多的线程进入共享资源空间;没有放过多的线程进入共享资源空间,但不同线程访问了同一份资源。
第二个问题需程序员通过代码维护,第一个问题可通过信号量解决。互斥量本质是一个计数器,对整体共享资源的局部资源数目做计数。线程进入共享资源需要对计数器做减操作(P 操作,本质对资源做申请),退出共享资源需要对计数器做加操作(V 操作,本质对资源做归还)。当计数器小于等于 0 时就不再允许线程进入共享资源。
线程要访问局部资源时要先申请信号量(P 操作),类似买电影票预定座位。信号量本身也是共享资源,其申请和释放操作必须是原子的。
二、信号量接口
初始化信号量
信号量在 semaphore.h 头文件中定义。sem_init 函数参数包括信号量类型 sem_t、设置信号量在进程中使用还是线程中使用(0 表示在线程中使用)、设置信号量计数器的初始值。成功返回 0,失败返回 -1。
销毁信号量
使用 sem_destroy 销毁信号量。
申请信号量(P 操作)
使用 sem_wait 对计数器做减操作。
释放信号量(V 操作)
使用 sem_post 对计数器做加操作。
三、基于环形队列的生产消费场景
环形队列
- 环形队列逻辑上是环形的,物理结构是数组。插入数据时下标 index++ 后模等队列元素个数。
- 有两个指针:头指针和尾指针。插入时头指针不动,尾指针++;删除时尾指针不动,头指针++。当头尾指针相等时,队列可能为空也可能为满。
- 判断空满可引入计数器统计元素个数。本文后续实现中,利用信号量的空间/数据计数特性,天然解决了环形队列的空满判断问题,无需额外维护计数器。
三种情况
将环形队列用于生产者消费者模型时,有三种主要情形:
- 队列为空:生产者和消费者访问同一个位置,必须让生产者先运行(同步),且生产者运行时消费者不能干扰(互斥)。
- 队列为满:生产者和消费者访问同一个位置,必须让消费者先运行(同步),且消费者运行时生产者不能干扰(互斥)。
- 队列不为空也不为满:单生产单消费场景下,访问位置不同,可并发运行。
单生产单消费模型具体实现
利用信号量和环形队列实现单生产单消费模型。生产者关心空余空间资源,消费者关心数据资源。
假设环形队列有 10 个位置,定义两个信号量:space_sem 表示空余空间资源计数器,初始化为 10;data_sem 表示数据资源计数器,初始化为 0。
生产者生产时申请空间信号量(P),生产完成后释放数据信号量(V)。 消费者消费时申请数据信号量(P),消费完成后释放空间信号量(V)。
Sem.hpp
#include <iostream>
#include <semaphore.h>
class Sem {
public:
Sem(int initnum) : _initnum(initnum) {
sem_init(&_sem, 0, _initnum);
}
{
(&_sem);
}
{
(&_sem);
}
~() {
(&_sem);
}
:
_sem;
_initnum;
};


