【发布时间】:2013-09-05 17:24:22
【问题描述】:
The POSIX documentation (IEEE 1003.1, 2013) 对于pthread_cond_timedwait 函数说:
需要注意的是,当 pthread_cond_wait() 和 pthread_cond_timedwait() 没有错误返回时,关联的谓词可能仍然为假。同样,当 pthread_cond_timedwait() 返回超时错误时,由于超时到期和谓词状态更改之间不可避免的竞争,关联的谓词可能为真。
(强调我的)
我们都知道由条件变量控制的谓词应该在一个while循环中检查的故事,并且可能会出现虚假唤醒。但我的问题是关于不可避免这个词——它是一个强词。为什么这样的竞赛是无法避免的?
注意,如果不存在这样的竞争,我们可以只检查 pthread_cond_timedwait 是否超时;而不是再次检查谓词,然后才处理超时条件。 (当然,假设我们仅在 1)持有互斥锁时和 2)当谓词实际更改时收到信号。)
在持有“用户互斥锁”的情况下以原子方式检查我们是否被超时唤醒或收到信号是否足够?
例如,让我们考虑一个建立在 POSIX 之上的条件变量的实现。 (错误处理和初始化省略了,明显的空白可以补上)。
class CV
{
pthread_mutex_t mtx;
pthread_cond_t cv;
int waiters; // how many threads are sleeping
int wakeups; // how many times this cv got signalled
public:
CV();
~CV();
// returns false if it timed out, true otherwise
bool wait(Mutex *userMutex, struct timespec *timeout)
{
pthread_mutex_lock(&mtx);
waiters++;
const int oldWakeups = wakeups;
userMutex->unlock();
int ret; // 0 on success, non-0 on timeout
for (;;) {
ret = pthread_cond_timedwait(&mtx, &cv, timeout);
if (!(ret == 0 && wakeups == 0))
break; // not spurious
}
if (ret == 0) // not timed out
wakeups--;
pthread_mutex_unlock(&mtx);
userMutex->lock();
pthread_mutex_lock(&mtx);
waiters--;
if (ret != 0 && wakeups > oldWakeups) {
// got a wakeup after a timeout: report the wake instead
ret = 0;
wakeups--;
}
pthread_mutex_unlock(&mtx);
return (ret == 0);
}
void wake()
{
pthread_mutex_lock(&mtx);
wakeups = min(wakeups + 1, waiters);
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);
}
};
可以证明
- 如果
CV::wait报告超时,那么我们确实没有收到信号,因此谓词没有改变;还有那个 - 如果超时到期但我们在返回用户代码之前收到信号,并持有用户互斥锁,则我们报告唤醒。
上面的代码是否包含一些严重的错误?如果不是,那么说比赛是不可避免的标准是错误的,还是必须做一些我错过的其他假设?
【问题讨论】:
-
在真正的并行执行中,谓词可能在超时到期的同一时刻发生变化是不可避免的。那场比赛还不够吗?
-
不完全。更改谓词和从超时调用返回都需要互斥体处理(和信号)。我试图理解为什么标准说即使在超时的情况下也必须检查谓词(只要谁更改它也发出信号......),谈论这个不可避免的比赛。跨度>
-
仍然可能存在相同的即时超时和谓词更改——信号不是比赛的一部分。此外,一旦做出超时决定,服务员必须重新排队以再次获取互斥锁。然后也可能发生谓词变化。
-
这是奇怪的部分。我想通过下面的 sn-p 看到的是,一旦您需要用户互斥锁,您还可以检测您是否收到信号,并以唤醒返回。
标签: c++ multithreading posix