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

时间:2013-02-17 18:25:53

标签: c pthreads mutex

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

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

3 个答案:

答案 0 :(得分:108)

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

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

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

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

例如,等待布尔标志fSet的一段显而易见的错误的代码:

bool fSet = false;

int WaitForTrue()
{
    while (!fSet)
    {
        sleep(n);
    }
}

我应该明白主要问题是谓词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);
}

嗯,这看起来很简单..现在我们从不评估谓词而不首先获得它的独占访问权限(通过锁存互斥锁)。但这仍然是一个主要问题。我们锁定了互斥锁,但我们永远不会释放它,直到我们的循环完成。如果其他人按照规则进行操作并在评估或修改fSet之前等待互斥锁,则在我们放弃互斥锁之前,他们永远无法执行此操作。唯一的#34;某人"在这种情况下可以做到的就是 us

那么为此添加更多图层呢?这有用吗?

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);
}

嗯,是的,它会"工作",但仍然没有好多少。 XXXXXYYYYY之间的时间段我们不拥有互斥锁(这是好的,因为我们无论如何都不会检查或修改fSet)。但是在那个时期的任何时候,其他一些线程都可以(a)获得互斥锁,(b)修改fSet,以及(c)释放互斥锁,在我们完成之前我们不会对它有所了解。 sleep(),再次获取互斥锁,并循环进行另一次检查。

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


行动中的条件变量

输入条件变量+互斥量对。互斥锁保护对更改或检查谓词的访问权限,而条件变量则建立一个监视变更的系统,更重要的是,原子地(就你而言) #39;反思,无论如何)与谓词互斥:

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);
}

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

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);

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

除非互斥锁被锁定,否则永远不要更改,或检查,谓词条件。的 自从


简单的监控线程

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

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;
}

更复杂的监控线程

修改此基本表单以考虑通知系统,该系统在您收到通知后不会要求您保留互斥锁,但会更加复杂,但是不是很多。下面是一个监视器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;
}

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

这比我希望的要长得多,但对于学习pthread编程的人来说这是一个主要的障碍,我觉得值得花费额外的时间/精力。我希望你能从中获得一些东西。

答案 1 :(得分:33)

当第一个线程调用pthread_cond_wait(&cond_t, &mutex);时,它会释放互斥锁并等待状态cond_t发出完整信号并且 mutex可用。

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

答案 2 :(得分:6)

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