我从操作系统概念的信号量方面难以理解监视器的实现
5.8.3使用信号量实现监视器
我们现在考虑监视机制的可能实现
使用信号量。
为每个监视器提供了信号量互斥锁(初始化为1)。一个
进程必须在进入监视器之前执行wait(mutex),并且必须
离开显示器后执行signal(mutex)。
由于信令过程必须等到恢复的过程离开或等待为止,因此引入了另一个信号量next
,
初始化为0。信令过程可以使用next
挂起
他们自己。还提供了一个整数变量next_count
进行计数
在next
上挂起的进程数。因此,每个外部
函数F
被替换为
wait(mutex);
...
body of F
...
if (next count > 0)
signal(next);
else
signal(mutex);
确保在监视器内相互排斥。
现在我们可以描述以及如何实现条件变量。
对于每种条件
x
,我们引入一个信号量x_sem
和一个整数变量
x_count
,都初始化为0。现在可以将x.wait()
操作实现为x_count++;
if (next_count > 0)
signal(next);
else
signal(mutex);
wait(x sem);
x_count--;
可以将
x.signal()
操作实现为if (x_count > 0) {
next_count++;
signal(x_sem);
wait(next);
next_count--;
}
引入信号量
next
和暂停在next_count
上的进程的计数next
的原因是什么?为什么按原样实现
x.wait()
和x.signal()
?谢谢。
最佳答案
-------注意-------
WAIT()和 SIGNAL()表示对监视器方法的调用
wait()和 signal()表示对信号量方法的调用,在以下说明中。
-------注释结尾-------
我认为如果您举一个具体的例子,会更容易。但是在此之前,让我们首先尝试了解什么是监视器。如书中所述,监视器是抽象数据类型,意味着它不是可用于实例化变量的真实类型。相反,它就像是具有一些规则和准则的规范,基于这些规则和准则,不同的语言可以为进程同步提供支持。
信号量是作为基于软件的解决方案而引入的,用于在基于硬件的方法(如TestAndSet()或Swap())上实现同步。即使有信号灯,程序员也必须确保以正确的顺序正确地调用 wait()和signal()方法。因此,引入了一个称为监视器的抽象规范,将与同步相关的所有这些内容封装为一个原语,因此,在监视器内执行的任何进程都将确保相应地使用这些方法(信号灯等待和信号)。
使用监视器时,所有共享变量和函数(使用共享变量)都将放入监视器结构,并且在调用这些函数中的任何一个时,监视器实现均应确保确保共享资源受到保护,以防止相互排斥和任何同步问题。
现在,使用监视器来监视信号量或其他同步技术,我们不再只处理关键部分的一部分,而是使用不同的函数来处理其中的许多部分。此外,我们还具有在这些函数中访问的共享变量。对于监视器中的每个不同功能,以确保仅执行其中一个功能,并且不对任何一个功能执行其他进程,我们可以使用称为互斥体的全局信号量。
考虑下面使用监视器解决餐饮哲学家问题的解决方案示例。
monitor dining_philopher
{
enum {THINKING, HUNGRY, EATING} state[5];
condition self[5];
void pickup(int i) {
state[i] = HUNGRY;
test(i);
if (state[i] != EATING)
self[i].WAIT();
}
void putdown(int i) {
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
}
void test(int i) {
if (
(state[(i + 4) % 5] != EATING) &&
(state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING))
{
state[i] = EATING;
self[i].SIGNAL();
}
}
initialization code() {
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}
}
理想情况下,流程如何调用这些功能应按以下顺序进行:
DiningPhilosophers.pickup(i);
...
// do somework
...
DiningPhilosophers.putdown(i);
现在,虽然在 Pickup()方法内部执行一个进程,但另一个进程可能尝试调用 putdown()(甚至是Pickup)方法。为了确保互斥,我们必须确保在任何给定时间仅在监视器内部运行一个进程。因此,要处理这些情况,我们有一个全局信号量互斥量,它封装了所有可调用(拾取和放下)方法。因此,这两种方法将实现如下:
void pickup(int i) {
// wait(mutex);
state[i] = HUNGRY;
test(i);
if (state[i] != EATING)
self[i].WAIT();
// signal(mutex);
}
void putdown(int i) {
// wait(mutex);
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
// signal(mutex);
}
现在,只有一个进程能够以其任何方法在监视器内部执行。现在,通过此设置,如果Process P1 已执行 Pickup()(但尚未将tp 放下筷子),然后处理 P2 (例如相邻的小餐馆),则尝试 Pickr(因为)他/她的筷子(共享资源)正在使用中,必须使用 wait()才能使用。让我们看看监视器的条件变量的 WAIT 和 SIGNAL 实现:
WAIT(){
x_count++;
if (next_count > 0)
signal(next);
else
signal(mutex);
wait(x_sem);
x_count--;
}
SIGNAL() {
if (x_count > 0) {
next_count++;
signal(x_sem);
wait(next);
next_count--;
}
}
条件变量的WAIT实现与信号量的实现不同,因为它必须提供更多的功能,例如允许其他进程通过释放互斥量全局信号量来调用监视器的功能(等待时)。因此,当 P2 从 Pick()方法调用WAIT时,它将调用 signal(mutex),从而允许其他进程调用监视方法,并针对特定于信号量的信号调用 wait(x_sem)有条件的。现在, P2 在这里被阻止了。另外,变量 x_count 会跟踪等待条件变量(自身)的进程数。
因此,当 P1 调用 putdown()时,这将通过 test()方法调用SIGNAL。在SIGNAL内,当 P1 在其持有的筷子上调用信号(x_sem)时,它必须另外做一件事。它必须确保监视器中仅运行一个进程。如果只调用信号(x_sem),则从那时起 P1 和 P2 都将开始在显示器内部进行操作。为防止此 P1 ,在释放其筷子后,它将自行阻塞,直到 P2 完成。为了阻止自身,它使用信号量和下一个。并使用计数器 next_count 来通知 P2 或其他进程有人被阻止。
因此,现在 P2 将得到筷子,并且在退出 Pickup()方法之前,它必须释放正在等待 P2 完成的 P1 。因此,现在,我们必须按以下方式更改 Pickup()方法(以及监视器的所有功能):
void pickup(int i) {
// wait(mutex);
state[i] = HUNGRY;
test(i);
if (state[i] != EATING)
self[i].WAIT();
/**************
if (next_count > 0)
signal(next);
else
signal(mutex);
**************/
}
void putdown(int i) {
// wait(mutex);
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
/**************
if (next_count > 0)
signal(next);
else
signal(mutex);
**************/
}
因此,现在,在任何进程退出监视器功能之前,它会检查是否有任何等待的进程,如果有,则释放它们,而不是互斥体全局信号量。最后的此类等待进程将释放互斥锁信号量,允许新进程进入监视功能。
我知道这很长,但是我花了一些时间来理解并希望将其写成书面形式。我将很快在博客上发布它。
如果有任何错误,请通知我。
最好,
沙比尔
关于operating-system - 为什么以这种方式在信号量方面实现监视器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46919797/