pthread_cond_wait(&cond_t,&mutex); 解锁然后锁定互斥锁?

MOH*_*MED 38 c mutex pthreads

pthread_cond_wait(&cond_t, &mutex);在我的程序中使用,我想知道为什么这个函数需要作为第二个参数mutex变量.

是否pthread_cond_wait()在开始时解锁互斥锁(执行开始pthread_cond_wait())然后在完成时(就在离开之前pthread_cond_wait())锁定?

Who*_*aig 119

关于条件变量及其用法的主题有很多文本,所以我不会给你带来大量丑陋的细节.它们存在的原因是允许您通知谓词状态的更改.以下对于理解条件变量及其互斥关联的正确使用至关重要:

  • pthread_cond_wait()同时解锁互斥锁开始等待条件变量发出信号.因此,在调用互斥锁之前,必须始终拥有互斥锁的所有权.

  • pthread_cond_wait()在互斥锁被锁定的情况下返回,因此您必须解锁互斥锁,以便在完成后使用其他地方.返回是否因为条件变量已发出信号而发生是不相关的.您仍然需要检查您的谓词,无论是否考虑到潜在的虚假唤醒.

  • 互斥的目的不是保护条件变量; 它是为了保护条件变量被用作信令机制的谓词.这是pthread条件变量及其互斥体最常被误解的习惯用法.条件变量不需要互斥保护; 谓词数据确实如此.将谓词视为外部状态,由条件变量/互斥体对的用户监视.

例如,等待布尔标志的一段微不足道但却明显错误的代码fSet:

bool fSet = false;

int WaitForTrue()
{
    while (!fSet)
    {
        sleep(n);
    }
}
Run Code Online (Sandbox Code Playgroud)

我应该明白主要问题是谓词fSet,根本没有受到保护.很多事情都可能出错.例如:从评估while-conditon到开始等待(或旋转,或其他)的时间,值可能已经改变.如果错过了那个改变通知,你就会不必要地等待.

我们可以稍微改变一下,所以至少谓词会以某种方式受到保护.修改评估谓词的相互排除很容易提供(还有什么)互斥.

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool fSet = false;

int WaitForTrue()
{
    pthread_mutex_lock(&mtx);
    while (!fSet)
        sleep(n);
    pthread_mutex_unlock(&mtx);
}
Run Code Online (Sandbox Code Playgroud)

好吧,这看起来很简单..现在我们从不评估谓词而不首先获得它的独占访问权限(通过锁存互斥锁).但这仍然是一个主要问题.我们锁定了互斥锁, 但在循环结束之前我们永远不会释放它.如果其他人按照规则进行操作并在评估或修改之前等待互斥锁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);
}
Run Code Online (Sandbox Code Playgroud)

嗯,是的,它会"奏效",但仍然没有好多少.之间的时间段XXXXXYYYYY我们不拥有互斥锁(这是好的,因为我们还没有检查或修改fSet).但是在那个时期的任何时候,其他一些线程可以(a)获得互斥体,(b)修改fSet,以及(c)释放互斥体,在我们完成之前我们不会知道它的事情sleep(),一次又一次获得互斥体锁定,并循环进行另一次检查.

是一个更好的办法.不知怎的,应该有一种方法可以释放互斥锁开始等待某种信号,告诉我们谓词的变化可能已经发生.同样重要的是,当我们收到该信号并返回到我们的代码时,我们应该已经拥有了授予我们访问权限以检查谓词数据的锁.这正是条件变量旨在提供的.


行动中的条件变量

输入条件变量+互斥量对.互斥锁保护对更改或检查谓词的访问,而条件变量建立了一个监视变化的系统,更重要的是,通过谓词互斥,以原子方式(无论如何,你这么做)这样做:

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);
}
Run Code Online (Sandbox Code Playgroud)

对于其他一些线程来表示上面的循环,有几种方法可以做到,下面两个最受欢迎:

pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cv);
Run Code Online (Sandbox Code Playgroud)

其他方式...

pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);
Run Code Online (Sandbox Code Playgroud)

每个都有不同的内在行为,我邀请你做一些关于这些差异的功课,并确定哪个更适合特定情况.前者以引入可能无根据的唤醒为代价提供更好的程序流程.后者减少了这些唤醒,但代价是较少的背景协同作用.两者都可以在我们的示例中使用,您可以尝试各自如何影响您的等待循环.无论如何,有一件事是至关重要的,而且这两种方法都能完成这项任务:

除非互斥锁被锁定,否则切勿更改或检查谓词条件.曾经.


简单的监视线程

这种类型的操作在监视器线程中很常见,该线程作用于特定的谓词条件,(没有错误检查)通常看起来像这样:

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;
}
Run Code Online (Sandbox Code Playgroud)

更复杂的监视器线程

修改此基本表单以考虑一个通知系统,该通知系统不需要您在获取通知后保持互斥锁被锁定,这会更加复杂,但不是很多.下面是一个监视器proc,一旦我们确定已经服务(可以这么说),它们在常规处理过程中不会保持互斥锁.

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;
}
Run Code Online (Sandbox Code Playgroud)

哪里会有人使用类似的是?好吧,假设你的"谓词"是工作队列的"状态",还有一些标志告诉你停止循环和退出.在收到"不同"的通知后,您检查是否应该继续执行循环,并决定是否应该继续,从队列中弹出一些数据.修改队列需要锁定互斥锁(记住,它的"状态"是我们谓词的一部分).一旦我们弹出了我们的数据,我们在本地拥有它并且可以独立于队列状态处理它,所以我们释放互斥锁,做我们的事情,然后需要互斥量用于下一次复飞.有许多方法可以对上述概念进行编码,包括明智地使用pthread_cond_broadcast等等.但基本形式是可以理解的.

事实证明这比我希望的要长得多,但这是人们学习pthread编程的一个主要障碍,我觉得值得花费额外的时间/精力.我希望你能得到一些东西.

  • 这是我见过的最好的答案之一.非常感谢! (3认同)

Lih*_*ihO 34

当第一个线程调用pthread_cond_wait(&cond_t, &mutex);它时,释放互斥锁并等待,直到条件cond_t被指示为完成 mutex可用.

因此,当pthread_cond_signal在另一个线程中调用时,它不会"唤醒"等待的线程.mutex必须首先解锁,只有这样第一个线程才有可能获得锁定,这意味着"成功返回pthread_cond_wait互斥锁后,应该被锁定,并且应该由调用线程拥有."


Dai*_*jan 6

是的它解锁,等待条件满足,然后等待它可以重新获得通过的互斥锁.

  • @MohamedKALLEL更重要的是,它在解锁和开始等待状态下都是*原子*.这是条件变量和相关互斥体的根本原因. (3认同)
  • 为了澄清"原子"是什么意思,它意味着当任何其他线程可以观察到互斥锁被解锁时(即锁定互斥锁),调用`pthread_cond_wait`的线程*已经*在条件变量上被阻塞.因此,在调用线程已经被阻塞之前,不能对由互斥锁保护的状态进行任何改变,并且在进行这样的改变之后进行的任何未来信令将必然包括被阻塞的线程作为唤醒的候选者. (3认同)