信号量,也叫信号灯,是荷兰的计算机科学家发明,用来解决并发中的互斥和同步的问题,一般操作系统都实现了信号量,并提供了简单的调用方式。
信号量控制的是线程,即最小调度单元。
信号量有三个操作:
struct semaphore {
int count;
queueType queue;
}
void acquire(semaphore s) {
s.count--;
if (s.count < 0) {
put this process in s.queue;
block this process;
}
}
void release(semaphore s) {
s.count++;
if (s.count <= 0) {
remove a process P from s.queue;
put process P in ready list;
}
}
过程
P和V都是由操作系统和硬件来保证的原子操作,且不会被中断。
最开始只有二元信号量(二进制信号量),即0和1,是为了解决互斥的问题。后来扩展到多值信号量,或计数信号量,可以用来解决同步的问题。
从上面的代码中,我们可以得出,如果将count初始值设置为1,那么只有一个线程可以得到资源,且只有等它释放了资源,其他线程才能得到资源,这就实现了互斥的效果。
如果将count初始值设置为0,并伴有如下过程。
s == -1,会被阻塞。s == 0,线程A被唤醒。当然,count可以被设置为更大的值,比如100,于是就有了带有缓冲区的生产者消费者模型。
生产者 --> buffer --> 消费者
针对这些需求,我们可以设置三个信号量。
class BoundedBuffer {
// 互斥锁,解决问题2
mutex = new semaphore(1);
// 计数器,初始值为0,用以判断缓冲区是否为空,为0就是空了
consumeMutex = new semaphore(0);
// 计数器,初始值为n,用以判断缓冲区是否满了,为0就是满了
produceMutex = new semaphore(n);
}
然后使用伪代码来表示这个过程。
function Produce(t) {
produceMutex->P();
mutex->P();
add t task to buffer
mutex->V();
consumeMutex->V();
}
function Consume() {
consumeMutex->P();
mutex->P();
remove t task to buffer
mutex->V();
produceMutex->V();
}