It is important to note that when pthread_cond_wait() and pthread_cond_timedwait() return without error,the associated predicate may still be false. Similarly,when pthread_cond_timedwait() returns with the timeout error,the associated predicate may be true due to an unavoidable race between the expiration of the timeout and the predicate state change.
(强调我的)
我们都知道应该在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); } };
有可能表明这一点
>如果CV :: wait报告超时,那么我们没有收到信号,因此谓词没有改变;然后
>如果超时到期但我们在返回用户代码并保持用户互斥之前发出信号,那么我们会报告唤醒.
解决方法
pthread_mutex_unlock(&mtx); // Trouble is here userMutex->lock(); pthread_mutex_lock(&mtx);
在评论点,任何事情都可能发生.你没有锁.条件变量的强大之处在于它们始终持有锁或等待.
然后就是手头的问题,不可避免的比赛
if (ret != 0 && wakeups > oldWakeups) { // got a wakeup after a timeout: report the wake instead ret = 0; wakeups--; }
无法保证一堆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}
此时,在线程1上,oldWakeups = wakeups,因此检查不可避免的竞赛案例未能注意到竞赛案例,重新创建了不可避免的竞赛案例.这是由于线程3窃取了针对thread1的信号,使得线程3(真正的超时)看起来像一个信号,而thread1(一个竞争信号/超时)看起来像一个超时