web-dev-qa-db-ja.com

pthread_cond_wait(&cond_t、&mutex);ミューテックスをロック解除してからロックしますか?

私は自分のプログラムでpthread_cond_wait(&cond_t, &mutex);を使用していますが、なぜこの関数が2番目のパラメーターとしてmutex変数を必要とするのか疑問に思っています。

pthread_cond_wait()は、開始時にmutexのロックを解除し(実行pthread_cond_wait())開始し、終了時に(pthread_cond_wait()を終了する直前に)ロックしますか?

38
MOHAMED

最初のスレッドが pthread_cond_wait(&cond_t, &mutex); を呼び出すと、mutexが解放され、条件cond_tが完了およびmutexが利用可能です。

そのため、他のスレッドで pthread_cond_signal が呼び出された場合、まだ待機しているスレッドを「ウェイクアップ」しません。 mutexを最初にロック解除する必要があります。その場合のみ、最初のスレッドがロックを取得する可能性があります。つまり、" pthread_cond_wait mutexロックされていて、呼び出しスレッドによって所有されているものとします。 "

33
LihO

manyというテキストが条件変数とその使用法の件名にありますので、たくさんのdetailsい詳細については説明しません。それらが存在する理由は、predicate状態の変化を通知できるようにするためです。以下は、条件変数の適切な使用とそれらのミューテックスの関連付けを理解する際のcriticalです。

  • pthread_cond_wait()同時にロック解除ミューテックスandは、条件変数が通知されるのを待ち始めます。したがって、alwaysを呼び出す前に、mutexの所有権が必要です。

  • pthread_cond_wait()はmutexlockedで返されるため、mutexのロックを解除して、終了時に他の場所で使用できるようにする必要があります。条件変数が通知されたために戻りが発生したかどうか関係ないスプリアスウェイクアップの可能性を考慮して、述部を確認する必要があります。

  • ミューテックスの目的は、条件変数を保護することですnot。条件変数がシグナリングメカニズムとして使用されているpredicateを保護するためです。これは、pthread条件変数とそのミューテックスの最もよく誤解されているイディオムの伝承です。条件変数には相互排他保護は必要ありません。述語データdoes。述語は、条件変数/ミューテックスのペアのユーザーによって監視されている外部状態と考えてください。

たとえば、些細でありながら明らかにwrongブールフラグfSetを待つコード:

bool fSet = false;

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

主な問題は述語fSetがまったく保護されていないことです。 多くここで問題が発生する可能性があります。例:while条件を評価してから、待機(または回転など)を開始するまでの値が変更された可能性があります。その変更通知が何らかの形でmissedである場合、不必要に待機しています。

少なくとも述語が何らかの形で保護されるように、これを少し変更できます。 andの両方の修正における相互排除は、(他の)ミューテックスで簡単に提供されます。

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool fSet = false;

int WaitForTrue()
{
    pthread_mutex_lock(&mtx);
    while (!fSet)
        sleep(n);
    pthread_mutex_unlock(&mtx);
}

さて、これは十分に単純なように思えます。今では、最初に排他的アクセスを取得しない限り(mutexをラッチすることによって)述部を評価することはありません。しかし、これは依然として大きな問題です。ミューテックスをラッチしましたただし、ループが終了するまでリリースしませんfSetの評価または変更の前に、他の全員がルールに従ってプレイし、ミューテックスロックを待機している場合、ミューテックスを放棄するまでそれらを実行することはできません。この場合、それを行うことができる唯一の「誰か」はsです。

では、これにさらにレイヤーを追加するのはどうでしょうか。これは機能しますか?

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()、もう一度mutexロックを取得し、ループして別のチェックを行います。

そこにhasより良い方法があります。どういうわけか、ミューテックスを解放する方法があるはずですand述語の変更が起こった可能性があることを知らせる何らかの信号を待ち始めます。同様に重要なことは、そのシグナルを受け取ってコードに戻るとき、述語データをチェックするためのアクセスを許可するロックをすでに所有している必要があるということです。これはexactly条件変数が提供するように設計されているものです。


アクション中の条件変数

条件変数とミューテックスのペアを入力します。ミューテックスは、変化するまたはchecking述語へのアクセスを保護しますが、条件変数は、変化を監視するシステムをセットアップし、さらに重要なこととして、atomically(とにかく)述語相互排除に関係している:

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

他のスレッドが上記のループを通知するために、いくつかの方法がありますが、最も一般的な方法は次の2つです。

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

それぞれ固有の動作が異なるため、これらの違いについていくつかの宿題を行い、特定の状況に適している方を判断してください。前者はpotentially不当なウェイクアップを導入することを犠牲にして、より良いプログラムフローを提供します。後者は、これらのウェイクアップを減らしますが、コンテキストシナジーが少なくなります。 Eitherはサンプルで機能し、それぞれが待機ループにどのように影響するかを試すことができます。いずれにせよ、一つのこと、そしてbothメソッドがこの任務を果たします:

変更しない、nor check、ミューテックスがロックされていない場合の述語条件Ever


単純な監視スレッド

このタイプの操作は、特定の述語条件で動作するmonitorスレッドで一般的です。これは、通常(次のようなエラーチェック)次のようになります。

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

より複雑な監視スレッド

notificationシステムを説明するためにこの基本フォームを変更すると、通知を受け取った後はミューテックスをラッチしたままにする必要がなくなりますが、それほど複雑ではありません。以下は、サービスが提供された(いわば)を確立した後、通常の処理中にmutexをラッチしないモニタープロシージャです。

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

誰かがthatのようなものをどこで使用しますか?さて、あなたの「述語」が作業キューの「状態」であり、ループを停止して終了するように指示するフラグであるとします。何かが「異なる」という通知を受け取ったら、ループの実行を続行する必要があるかどうかを確認し、続行する必要があると判断して、キューからデータをポップします。キューを変更するには、mutexをラッチする必要があります(その「状態」は述語の一部であることに注意してください)。データをポップしたら、それをローカルで処理し、キュー状態のindependentを処理できます。そのため、ミューテックスを解放して処理を行い、次のゴーアラウンドにミューテックスを要求します。 pthread_cond_broadcastの賢明な使用など、上記の概念をコーディングする方法は多数ありますが、基本的な形式は理解できると思います。

これは私が期待していたよりもかなり長いことが判明しましたが、これはpthreadプログラミングを学ぶ人にとってはmajorハードルであり、余分な時間/労力をかける価値があると感じています。あなたはそれから何かを得たと思います。

113
WhozCraig

はい、ロックを解除し、条件が満たされるまで待機してから、渡されたミューテックスを取得できるまで待機します。

6
Daij-Djan