pthread_cond_wait(&cond_t,&mutex); 解锁,然后locking互斥锁?
我正在使用pthread_cond_wait(&cond_t, &mutex);
在我的程序,我想知道为什么这个函数需要作为第二个参数互斥variables。
pthread_cond_wait()
在开始(开始执行pthread_cond_wait()
)时解锁互斥锁,并在完成时locking互斥锁(就在离开pthread_cond_wait()
之前)?
当第一个线程调用pthread_cond_wait(&cond_t, &mutex);
它释放互斥锁并等待,直到条件cond_t
被发信号完成, mutex
可用。
所以当在另一个线程中调用pthread_cond_signal
时,它不会“唤醒”等待的线程。 必须首先解锁mutex
,只有这样第一个线程才有可能获得一个锁,这意味着“成功返回pthread_cond_wait
互斥锁后,该锁应该被locking,并由该调用线程拥有”。
关于条件variables和它们的用法有很多文字,所以我不会给你带来很多丑陋的细节。 它们存在的原因是允许你通知谓词状态的变化。 以下对于理解正确使用条件variables及其互斥关联至关重要 :
-
pthread_cond_wait()
同时解锁互斥锁, 并开始等待条件variables发出信号。 因此在调用之前,您必须始终拥有互斥体的所有权。 -
pthread_cond_wait()
返回时locking了互斥锁 ,因此在完成互斥锁时 ,必须解锁互斥锁以允许其用于其他位置。 是否因为条件variables被发信号而发生返回是不相关的 。 您仍然需要检查谓词,而不考虑潜在的虚假唤醒 。 -
互斥锁的目的不是保护条件variables; 它是保护条件variables被用作信号机制的谓词 。 这是最常被误解的pthread条件variables及其互斥体的成语。 条件variables不需要互斥保护; 谓词数据呢。 将谓词看作是由状态variables/互斥对的用户监视的外部状态。
例如,一个微不足道但显然是错误的代码等待一个布尔标志fSet
:
bool fSet = false; int WaitForTrue() { while (!fSet) { sleep(n); } }
我应该很明显,主要的问题是谓词fSet
就没有被保护。 这里有很多事情可能会出错。 例如:从评估时间到你开始等待(或者旋转,或者其他)时,价值可能已经改变了。 如果这个变化通知不知何故错过了 ,你就不必要的等待。
我们可以改变这一点,所以至less这个谓词是被保护的。 在修改和评估谓词时相互排斥很容易提供(另外)一个互斥体。
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; bool fSet = false; int WaitForTrue() { pthread_mutex_lock(&mtx); while (!fSet) sleep(n); pthread_mutex_unlock(&mtx); }
那么,这似乎够简单了..现在我们从来没有首先获得独占访问(通过locking互斥体)评估谓词。 但这仍然是一个主要问题。 我们锁住了互斥锁, 但是直到我们的循环结束之前 , 我们永远不会释放它 。 如果其他人玩游戏规则,并在评估或修改fSet
之前等待互斥锁,那么在我们放弃互斥体之前,他们将永远无法这样做。 在这种情况下唯一能够做到的“人”就是我们 。
那么如何增加更多的层次呢? 这会工作吗?
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; bool fSet = false; int WaitForTrue() { pthread_mutex_lock(&mtx); while (!fSet) { pthread_mutex_unlock(&mtx); // XXXXX sleep(n); // YYYYY pthread_mutex_lock(&mtx); } pthread_mutex_unlock(&mtx); }
那么,是的,它会“工作”,但仍然不是好多了。 从XXXXX
到YYYYY
之间的时间段我们并不拥有互斥量(这是可以的,因为我们并没有检查或修改fSet
)。 但是在这段时间内,其他线程可以(a)获取互斥锁,(b)修改fSet
,(c)释放互斥锁,直到完成我们的sleep()
再次获得互斥锁,并循环进行另一次检查。
必须有更好的方法。 不知何故,应该有一种方法,我们可以释放互斥体, 并开始等待某种信号,告诉我们可能已经发生谓词的变化。 同样重要的是,当我们收到这个信号并返回到我们的代码时,我们应该已经拥有了授予我们访问权限的锁来检查谓词数据。 这正是条件variablesdevise提供的。
条件variables在行动
input条件variables+互斥对。 互斥锁保护对变化或检查谓词的访问,而条件variables则build立了一个监视变化的系统,更重要的是,这样做是通过谓词互斥来自动进行的 (就你所关心的而言)
int WaitForPredicate() { // lock mutex (means:lock access to the predicate) pthread_mutex_lock(&mtx); // we can safely check this, since no one else should be // changing it unless they have the mutex, which they don't // because we just locked it. while (!predicate) { // predicate not met, so begin waiting for notification // it has been changed *and* release access to change it // to anyone wanting to by unlatching the mutex, doing // both (start waiting and unlatching) atomically pthread_cond_wait(&cv,&mtx); // upon arriving here, the above returns with the mutex // latched (we own it). The predicate *may* be true, and // we'll be looping around to see if it is, but we can // safely do so because we own the mutex coming out of // the cv-wait call. } // we still own the mutex here. further, we have assessed the // predicate is true (thus how we broke the loop). // take whatever action needed. // You *must* release the mutex before we leave. Remember, we // still own it even after the code above. pthread_mutex_unlock(&mtx); }
对于其他一些线程来说明上面的循环,有几种方法可以做到这一点,下面两个最stream行的方法:
pthread_mutex_lock(&mtx); TODO: change predicate state here as needed. pthread_mutex_unlock(&mtx); pthread_cond_signal(&cv);
其他方式…
pthread_mutex_lock(&mtx); TODO: change predicate state here as needed. pthread_cond_signal(&cv); pthread_mutex_unlock(&mtx);
每个人都有不同的内在行为,我请你们就这些差异做一些功课,并确定哪些更适合具体情况。 前者提供更好的程序stream程,而不会引入潜在的不必要的唤醒。 后者减less了这些起床,但是以较less的上下文协同作用为代价。 要么在我们的示例中工作,并且可以尝试各自如何影响您的等待循环。 无论如何,有一件事是最重要的,而且这两种方法都能完成这个任务:
除非互斥锁被locking,否则不要更改谓词条件 。 永远 。
简单的监控线程
这种types的操作在一个监视器线程中是很常见的,这个监视器线程作用于一个特定的谓词条件,(无错误检查)通常看起来像这样:
void* monitor_proc(void *pv) { // acquire mutex ownership // (which means we own change-control to the predicate) pthread_mutex_lock(&mtx); // heading into monitor loop, we own the predicate mutex while (true) { // safe to check; we own the mutex while (!predicate) pthread_cond_wait(&cv, &mtx); // TODO: the cv has been signalled. our predicate data should include // data to signal a break-state to exit this loop and finish the proc, // as well as data that we may check for other processing. } // we still own the mutex. remember to release it on exit pthread_mutex_unlock(&mtx); return pv; }
更复杂的显示器线程
修改这个基本的表格来说明一个通知系统,当你拿起通知后 ,不需要你锁住互斥锁,就会变得更加复杂一些,但不是太多。 下面是一个监视器程序,一旦我们build立了服务器(可以这么说),就不会在常规处理过程中保持互斥锁。
void* monitor_proc(void *pv) { // acquire mutex ownership // (which means we own change-control to the predicate) pthread_mutex_lock(&mtx); // heading into monitor loop, we own the predicate mutex while (true) { // check predicate while (!predicate) pthread_cond_wait(&cv, &mtx); // some state that is part of the predicate to // inform us we're finished if (break-state) break; // TODO: perform latch-required work here. // unlatch the mutex to do our predicate-independant work. pthread_mutex_unlock(&mtx); // TODO: perform no-latch-required work here. // re-latch mutex prior to heading into wait pthread_mutex_lock(&mtx); } // we still own the mutex. remember to release it on exit pthread_mutex_unlock(&mtx); return pv; }
哪里有人会用这样的东西? 那么,假设你的“谓词”是工作队列的“状态”,还有一些标志告诉你停止循环和退出。 在收到某个“不同”的通知后,您会检查是否应该继续执行循环,并决定是否应该继续,从队列中popup一些数据。 修改队列需要locking互斥锁(记住,它的“状态”是我们谓词的一部分)。 一旦我们popup了我们的数据,我们就可以在本地使用它,并且可以独立于队列状态来处理它,所以我们释放互斥量,做我们的事情,然后需要互斥量来进行下一次复制。 有很多方法来编码上述概念,包括明智地使用pthread_cond_broadcast
等,但基本的forms是希望可以理解的。
结果比我想象的要长得多,但这是学习pthread编程的主要障碍,我觉得值得花费额外的时间/精力。 我希望你能从中得到一些东西。
是的,它解锁,等待条件满足,然后等待,直到它可以重新获得通过互斥。