-------注意-------
WAIT() 和 SIGNAL() 表示对监视器方法的调用
wait() 和 signal() 表示对信号量方法的调用,在下面的解释中。
-------笔记结束-------
我认为如果你从一个具体的例子来思考会更容易。但在此之前,让我们先尝试了解什么是显示器。正如书中所解释的,monitor 是一种抽象数据类型,这意味着它不是可用于实例化变量的真实类型。相反,它就像一个包含一些规则和指南的规范,基于这些规则和指南,不同的语言可以为进程同步提供支持。
信号量是作为一种基于软件的解决方案引入的,用于通过基于硬件的方法(如 TestAndSet() 或 Swap())实现同步。即使使用信号量,程序员也必须确保他们以正确的顺序和正确地调用 wait() 和 signal() 方法。因此,引入了一个名为 monitors 的抽象规范,将所有这些与同步相关的东西封装为一个原语,因此在监视器内执行的任何进程都将确保这些方法(信号量等待和信号) 调用。
使用监视器,所有共享变量和函数(使用共享变量)都被放入监视器结构中,当调用这些函数中的任何一个时,监视器实现会确保共享资源受到互斥和任何问题的保护同步。
现在有了监视器,与信号量或其他同步技术不同,我们不仅仅处理关键部分的一部分,而是根据不同的功能处理其中的许多部分。此外,我们还拥有可在这些函数中访问的共享变量。对于监视器中的每个不同函数,为了确保只执行其中一个函数并且没有其他进程在任何函数上执行,我们可以使用名为 mutex 的全局信号量。
考虑使用下面的监视器解决哲学家就餐问题的示例。
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) 方法。为了确保互斥,我们必须确保在任何给定时间只有一个进程在监视器内运行。因此,为了处理这些情况,我们有一个全局信号量 mutex,它封装了所有可调用的 (pickup & putdown) 方法。所以这两个方法会实现如下:
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);
}
现在只有一个进程能够以任何方法在监视器内执行。现在,通过此设置,如果进程 P1 已执行 pickup() (但尚未 放下 筷子) 然后处理 P2 (比如相邻的用餐者) 尝试 pickup():因为他/她的筷子 (共享资源) 正在使用中,它必须 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 实现不同于信号量的实现,因为它必须提供更多功能,例如 通过释放 mutex 允许其他进程调用监视器的功能(在等待的同时) 全局信号量。因此,当 P2 从 pickup() 方法调用 WAIT 时,它将调用 signal(mutex) 允许其他进程调用监视器方法并在特定于条件的信号量上调用 wait(x_sem)。现在,P2 在这里被屏蔽了。此外,变量 x_count 跟踪条件变量 (self) 上等待的进程数。
所以当 P1 调用 putdown() 时,这将通过 test() 方法调用 SIGNAL。在 SIGNAL 内部,当 P1 在它持有的筷子上调用 signal(x_sem) 时,它必须做一件事。它必须确保只有一个进程在监视器内运行。如果它只调用 signal(x_sem) 那么从那时起 P1 和 P2 都将开始在监视器内做事。为了防止这个P1,它在松开筷子后会阻塞自己,直到P2结束。为了阻止自己,它使用信号量next。为了通知 P2 或其他进程有人被阻止,它使用计数器 next_count。
所以,现在 P2 会拿到筷子,在它退出 pickup() 方法之前它必须释放等待 P1 的P1 strong>P2 完成。所以现在,我们必须将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);
**************/
}
所以现在,在任何进程退出监视器的功能之前,它会检查是否有任何等待进程,如果有则释放它们而不是 mutex 全局信号量。最后一个这样的等待进程将释放 mutex 信号量,允许新进程进入监控功能。
我知道它很长,但我花了一些时间才理解并想把它写下来。我会尽快将其发布在博客上。
如果有任何错误,请告诉我。
最好的,
沙比尔