该函数的 POSIX 文档(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
报告超时,那么我们没有收到信号,因此谓词没有改变;然后 - 如果超时到期但我们在返回用户代码之前收到信号,并持有用户互斥锁,则我们报告唤醒。
上面的代码是否包含一些严重的错误?如果不是,那么说比赛是不可避免的标准是错误的,还是它必须做一些我错过的其他假设?