48

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

pthread_cond_wait()開始時 (実行開始時) にミューテックスのロックを解除し、pthread_cond_wait()終了時 (終了直前) にロックしpthread_cond_wait()ますか?

4

3 に答える 3

143

条件変数とその使用法に関するテキストはたくさんあるので、見苦しい詳細をたくさん書いて退屈させるつもりはありません。それらが存在する理由は、述語状態の変化を通知できるようにするためです。条件変数の適切な使用とそのミューテックスの関連付けを理解するには、次のことが重要です。

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

  • pthread_cond_wait()ロックされたミューテックスで戻るため、ミューテックスのロックを解除して、使用が終了したときに他の場所で使用できるようにする必要があります。条件変数が通知されたためにリターンが発生したかどうかは関係ありません偽のウェイクアップの可能性を考慮して、述語をチェックする必要があります。

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

たとえば、ブール値フラグを待機するための些細な、しかし明らかに間違ったfSetコード:

bool fSet = false;

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

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

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

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をあきらめるまで、そうすることができません。この場合、それができる唯一の「誰か」は私たちです。

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

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()。ロックし、別のチェックのためにループします。

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


条件変数の動作

条件変数 + ミューテックス ペアを入力します。ミューテックスは述語の変更またはチェックへのアクセスを保護しますが、条件変数は変更を監視するシステムを設定し、さらに重要なことに、述語の相互排除を使用してアトミックに (とにかく、あなたに関する限り)監視します。

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

それぞれに固有の動作が異なります。これらの違いについて下調べを行い、特定の状況により適したものを判断してください。前者は、潜在的に不当なウェイクアップを導入することを犠牲にして、より良いプログラム フローを提供します。後者はこれらのウェイクアップを減らしますが、コンテキストの相乗効果が少なくなります。このサンプルではどちらも機能し、それぞれが待機ループにどのように影響するかを試すことができます。いずれにせよ、最も重要なことが 1 つあります。両方の方法がこの義務を満たします。

ミューテックスがロックされていない限り、述語条件を変更したり、チェックしたりしないでください。今まで


シンプルなモニター スレッド

このタイプの操作は、特定の述語条件で動作するモニタースレッドで一般的です。(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;
}

より複雑な監視スレッド

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

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 プログラミングを学ぶ人々にとって大きなハードルであり、余分な時間/労力を費やす価値があると感じています. あなたがそれから何かを得たことを願っています。

于 2013-02-17T19:30:28.300 に答える
37

最初のスレッドが呼び出すと、mutex が解放され、条件が完了たことが通知されて使用可能pthread_cond_wait(&cond_t, &mutex);になるまで待機します。cond_t mutex

そのpthread_cond_signalため、他のスレッドで が呼び出された場合、まだ待機しているスレッドを「起動」しません。mutex最初にロックを解除する必要があります。その場合にのみ、最初のスレッドがロックを取得する可能性があります。つまり、「ミューテックスが正常に返さpthread_cond_waitれると、ロックされ、呼び出しスレッドによって所有される」ことを意味します。

于 2013-02-17T18:33:53.313 に答える
6

はい、ロックを解除し、条件が満たされるのを待ってから、渡されたミューテックスを再取得できるようになるまで待ちます。

于 2013-02-17T18:27:29.260 に答える