Semaphore
信号量(semaphore) 是一种同步原始数据类型。 从程序员的角度来看,它是一种不透明的数据类型,有两个定义的操作,通常称为wait和signal。(译者注:可以理解为和PV操作对应,wait是P操作-尝试申请资源,signal是V操作-释放资源) 有两种类型的信号量--“二进制信号量”和“计数信号量”。
计数信号量
最常见的信号量类型是计数信号量。 可以将其视为单个整数变量。 当调用 signal 方法时,变量被原子 递增。 当调用wait方法时,进程将持续等待直到变量达到非零值,然后在返回之前对其进行原子递减。
二进制信号量
二进制信号量 (也称为Mutex-互斥) 就像计数信号量,不同之处在于 signal 方法将信号量的值设置为1,而不是将其递增。
用法
二进制信号量最常用于保证互斥。 在这种情况下,信号量值被初始化为1,并且一个应用程序即将进入代码的互斥部分调用 wait, 然后在退出代码后调用signal。
计数信号量可以用作实现许多同步模型的构建基础模块。 这方面的典型示例是静态分配的队列,如下面的示例所示。
Message queue[N];
Semaphore slots = new Semaphore(N);
Semaphore messages = new Semaphore(0);
int last_read = 0
int last_written = 0;
Message get()
{
Message m;
messages.wait();
m = queue[last_read];
last_read = (last_read+1)%N;
slots.signal();
return m;
}
void put(Message m)
{
slots.wait();
queue[last_written] = m;
last_written = (last_written+1)%N;
messages.signal();
}
在上面的示例中,这些函数可以使用错误代码来告诉调用方数组何时没有空间,以及队列何时为空 (或断言,由于空间不足而终止程序/使内核崩溃,并试图从空队列中get()内容)。 但是,在许多情况下,您只是希望当前线程阻塞,直到可以执行实际请求为止。 在这些情况下,你需要一个信号量。
有很多(很多很多)算法使用信号量作为同步原语。 当信号量用于同步同一进程的多个线程时,这种算法通常会结合二进制 (互斥) 和计数信号量。 这是因为操纵数据结构的代码仍然必须是互斥的,即使使用信号量来控制资源使用。
实现
虽然信号量的描述非常简单,但实现并不像最初听起来那样容易。 首先,确保这两种方法原子化运行并不总是容易的; 其次,出于效率的目的,应该从调度器队列中移除对值为0的信号量调用wait 的线程, 并且只有在信号量发出信号(signalled,译者注:V操作)时才再次添加。
因此,信号量的通常实现是作为内核级对象,它使用另一种更原始的锁定形式(例如自旋锁),以确保一次只能有一个线程访问它。 除了整数变量之外,它还包含一个等待访问的线程队列。 每当调用 signal 时,在递增变量后立即唤醒并重新安排队列前端的线程 (或者,在某些情况下,变量永远不会递增,唤醒的线程跳过递减的阶段; 这样做可以确保唤醒已有线程进行,而不是某些新到达的线程来抢占)。 每当 wait 无法立即进行时,当前线程将被挂起并在适当的位置添加到队列中 (例如,在末端,或在与线程优先级正确的位置)。
注意,这里有一个典型的竞争条件,称为lost wake-up(无法唤醒问题)问题。 有关此问题的解决方案,请阅读本文 http://www.linuxjournal.com/article/8144。
另见
相关文章
论坛主题
外部链接
- Semaphore on Wikipedia