none-busy-waiting
和[{1}}信号量的经典wait()
版本实现如下。在本节中,signal()
可能是否定的。
value
问题:以下版本是否也正确?在这里,我首先测试并修改值。如果你能告诉我一个不起作用的场景,那就太好了。
//primitive
wait(semaphore* S)
{
S->value--;
if (S->value < 0)
{
add this process to S->list;
block();
}
}
//primitive
signal(semaphore* S)
{
S->value++;
if (S->value <= 0)
{
remove a process P from S->list;
wakeup(P);
}
}
修改:
我的动机是弄清楚我是否可以使用后一种版本来实现互斥,没有死锁,没有饥饿。
答案 0 :(得分:1)
您的修改版本引入了竞争条件:
两个线程都获得了count = 1的信号量。哎呀。请注意,即使它们不可抢占也存在另一个问题(见下文),但为了完整性,这里讨论了原子性以及真正的锁定协议的工作原理。
使用这样的协议时,确切地确定使用的原子基元非常重要。原子基元是这样的,它们似乎是即时执行的,而不与任何其他操作交错。你不能只是把一个大功能称为原子;你必须使用其他原子基元以某种方式使原子。
大多数CPU提供称为“原子比较和交换”的原语。我将从这里缩写为cmpxchg。语义就像这样:
bool cmpxchg(long *ptr, long old, long new) {
if (*ptr == old) {
*ptr = new;
return true;
} else {
return false;
}
}
使用此代码 cmpxchg
未实现 。它在CPU硬件中,但行为有点像这样,只是原子的。
现在,让我们添加一些额外的有用功能(由其他原语构建):
以下是典型的信号量采集程序的外观。它比你的例子复杂一点,因为我已经明确地确定了我正在使用的原子操作:
void sem_down(sem *pSem)
{
while (1) {
long spec_count = pSem->count;
read_memory_barrier(); // make sure spec_count doesn't start changing on us! pSem->count may keep changing though
if (spec_count > 0)
{
if (cmpxchg(&pSem->count, spec_count, spec_count - 1)) // ATOMIC
return; // got the semaphore without blocking
else
continue; // count is stale, try again
} else { // semaphore count is zero
add_waitqueue(pSem->wqueue); // ATOMIC
// recheck the semaphore count, now that we're in the waitqueue - it may have changed
if (pSem->count == 0) schedule(); // NOT ATOMIC
remove_waitqueue(pSem->wqueue); // ATOMIC
// loop around again to try to acquire the semaphore
}
}
}
您会注意到,在真实世界的semaphore_down函数中,非零pSem->count
的实际测试是由cmpxchg
完成的。你不能相信任何其他阅读;读取值后,该值可能会立即更改。我们根本无法将值检查和值修改分开。
这里的spec_count
是推测。这个很重要。我基本上猜测计数是多少。这是一个非常好的猜测,但它是一个猜测。如果我的猜测错误,cmpxchg
将失败,此时例程必须循环并再次尝试。如果我猜0,那么我会被唤醒(因为它在等待时我不再为零),或者我会注意到它在计划测试中不再为零。
您还应该注意,没有办法制作包含阻塞操作原子的函数。这是荒谬的。根据定义,原子功能似乎是即时执行的,而不是与任何其他任何东西交错。但是根据定义,阻塞函数会等待其他事情发生。这是不一致的。同样,没有原子操作可以在阻塞操作中“拆分”,就像你的例子一样。
现在,您可以通过声明函数不可抢占来消除这种复杂性。通过使用锁或其他方法,您只需确保一次只能在信号量代码中运行一个线程(当然不包括阻塞)。但问题仍然存在。从值0开始,其中C将信号量下调两次,然后:
您可以通过循环重新检查S-&gt;值来解决此问题 - 再次假设您在单处理器计算机上并且您的信号量代码是可抢占的。不幸的是,这些假设在所有桌面操作系统上都是错误的:)
有关真正的锁定协议如何工作的更多讨论,您可能会对“Fuss, Futexes and Furwocks: Fast Userlevel Locking in Linux”文章感兴趣