pep*_*ppe 7 c++ multithreading posix
该函数的POSIX文档(IEEE 1003.1,2013)pthread_cond_timedwait说:
需要注意的是,当pthread_cond_wait()和pthread_cond_timedwait()无错误地返回时,关联的谓词可能仍然为false.类似地,当pthread_cond_timedwait()返回超时错误时,由于超时到期和谓词状态更改之间不可避免的竞争,关联的谓词可能为真.
(强调我的)
我们都知道应该在while循环中检查由条件变量控制的谓词的故事,并且可能存在虚假的唤醒.但我的问题是关于这个不可避免的词 - 这是一个强有力的词.为什么这样的比赛无法避免?
请注意,如果这样的比赛不存在,我们可以检查pthread_cond_timedwait是否超时; 而不是再次检查谓词,然后才处理超时条件.(假设,当然,我们仅通过持有的互斥锁发出信号1)和2)当谓词实际发生变化时.)
如果我们被超时唤醒或发出信号,那么用"用户mutex"进行原子检查是不够的?
例如,让我们考虑在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);
}
};
Run Code Online (Sandbox Code Playgroud)
有可能表明这一点
CV::wait报告超时,那么我们并没有得到信号,并因此断言没有改变 ; 然后上面的代码是否包含一些严重的错误?如果不是,说比赛是不可避免的是标准错误,还是必须做一些我错过的其他假设?
首先,请注意这通常有一个危险的部分:
pthread_mutex_unlock(&mtx);
// Trouble is here
userMutex->lock();
pthread_mutex_lock(&mtx);
Run Code Online (Sandbox Code Playgroud)
在评论点,任何事情都可能发生。你没有持有任何锁。条件变量的强大之处在于它们总是要么持有锁,要么等待。
那么接下来的问题就是不可避免的比赛
if (ret != 0 && wakeups > oldWakeups) {
// got a wakeup after a timeout: report the wake instead
ret = 0;
wakeups--;
}
Run Code Online (Sandbox Code Playgroud)
无法保证一堆 pthread_cond_t 的等待会以什么顺序被唤醒,这会对您的计数造成严重破坏
Thread1 Thread2 Thread3
{lock userMtx in calling code}
{lock mtx}
waiters++ (=1)
oldWakeups = 0
{unlock userMtx }
wait {unlock mtx}
{lock userMtx in calling code}
{lock mtx}
signal_all
wakeups = 1
{unlock mtx}
{unlock userMtx in calling code}
timeout(unavoid. racecase) {lock mtx}
{unlock mtx}
{lock userMtx in calling code}
{lock mtx}
waiters++ (=2)
oldWawkupes = 1
{unlock userMtx }
wait {unlock mtx}
timeout {lock mtx}
{unlock mtx}
{lock userMtx}
{lock mtx}
waiters-- (=1)
wakeups-- (=0)*
{unlock mtx}
{unlock userMtx in calling code}
{lock userMtx}
{lock mtx}
waiters--(=0)
wakeups == oldWakeups (=0)
{unlock mtx}
{unlock userMtx in calling code}
Run Code Online (Sandbox Code Playgroud)
此时,在线程 1 上,oldWakeups =wakeups,因此对不可避免的争用情况的检查未能注意到该争用情况,从而重新创建了不可避免的争用情况。这是由于线程 3 窃取了线程 1 的信号,使得线程 3(真正的超时)看起来像信号,而线程 1(竞争信号/超时)看起来像超时