web-dev-qa-db-ja.com

Linuxで複数の条件変数を不必要なスリープなしで待機しますか?

実際には、複数の条件変数を同時に待機したい、レイテンシに敏感なアプリを書いています。 Linuxでこの機能を実現する方法をいくつか読んだことがありますが(これはWindowsに組み込まれているようです)、どれも私のアプリに適しているようには見えません。私が知っている方法は次のとおりです。

  1. 待機する条件変数のそれぞれで1つのスレッドを待機させます。ウェイクすると、代わりに待機する単一の条件変数を通知します。

  2. 時限待機で複数の条件変数を循環します。

  3. 代わりにファイルまたはパイプにダミーバイトを書き込み、それらをポーリングします。

#1と#2は不必要な睡眠を引き起こすため、不適切です。 #1では、ダミースレッドがウェイクアップするのを待ってから、リアルスレッドにシグナルを送信し、実際のスレッドがウェイクアップするのではなく、最初にウェイクアップするのではなく、追加のスケジューラークォンタムを費やします。これは実際に私のアプリにとって重要であり、本格的なRTOSを使用する必要がないことを望みます。 #2はさらに悪く、N *のタイムアウト時間をスリープ状態で過ごす可能性があります。タイムアウトが0になると、スリープ状態になることはありません(CPUを無限に燃やして他のスレッドを枯渇させることも悪いことです)。

#3の場合、「シグナル」されているスレッドがビジーまたはクラッシュした場合(実際には、スレッドではなく個別のプロセスを処理しているため、ミューテックスと条件は共有メモリに格納されるため)、パイプに問題があります。他のクライアントと同様に、パイプのバッファがいっぱいになるため、スレッドはスタックします。アプリの実行時間が長くなると、ファイルが無限に大きくなるため、ファイルには問題があります。

これを行うより良い方法はありますか? Solarisにも適切な回答に興味があります。

27
Joseph Garvin

POSIXスレッドについて話している場合は、単一の条件変数とイベントフラグの数などを使用することをお勧めします。アイデアは、イベント通知を保護するためにピアcondvar mutexを使用することです。とにかく、cond_wait()の終了後にイベントを確認する必要があります。これは私のトレーニングからこれを説明するのに十分古いコードです(そうです、実行することを確認しましたが、それは少し前に準備されたものであり、新参者のために急いでいることに注意してください)。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static pthread_cond_t var;
static pthread_mutex_t mtx;

unsigned event_flags = 0;
#define FLAG_EVENT_1    1
#define FLAG_EVENT_2    2

void signal_1()
{
    pthread_mutex_lock(&mtx);
    event_flags |= FLAG_EVENT_1;
    pthread_cond_signal(&var);
    pthread_mutex_unlock(&mtx);
}

void signal_2()
{
    pthread_mutex_lock(&mtx);
    event_flags |= FLAG_EVENT_2;
    pthread_cond_signal(&var);
    pthread_mutex_unlock(&mtx);
}

void* handler(void*)
{
    // Mutex is unlocked only when we wait or process received events.
    pthread_mutex_lock(&mtx);

    // Here should be race-condition prevention in real code.

    while(1)
    {
        if (event_flags)
        {
            unsigned copy = event_flags;

            // We unlock mutex while we are processing received events.
            pthread_mutex_unlock(&mtx);

            if (copy & FLAG_EVENT_1)
            {
                printf("EVENT 1\n");
                copy ^= FLAG_EVENT_1;
            }

            if (copy & FLAG_EVENT_2)
            {
                printf("EVENT 2\n");
                copy ^= FLAG_EVENT_2;

                // And let EVENT 2 to be 'quit' signal.
                // In this case for consistency we break with locked mutex.
                pthread_mutex_lock(&mtx);
                break;
            }

            // Note we should have mutex locked at the iteration end.
            pthread_mutex_lock(&mtx);
        }
        else
        {
            // Mutex is locked. It is unlocked while we are waiting.
            pthread_cond_wait(&var, &mtx);
            // Mutex is locked.
        }
    }

    // ... as we are dying.
    pthread_mutex_unlock(&mtx);
}

int main()
{
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&var, NULL);

    pthread_t id;
    pthread_create(&id, NULL, handler, NULL);
    sleep(1);

    signal_1();
    sleep(1);
    signal_1();
    sleep(1);
    signal_2();
    sleep(1);

    pthread_join(id, NULL);
    return 0;
}
12

#3オプション(代わりにファイルまたはパイプにダミーバイトを書き込み、それらをポーリングする)は、Linuxではより良い代替手段です eventfd

eventfdを使用すると、制限されたサイズのバッファー(パイプなど)または無限に拡大するバッファー(ファイルなど)の代わりに、カーネル内の署名されていない64ビットカウンターがあります。 8バイトのwriteは、カウンターに数値を追加します。 8バイトのreadは、カウンターをゼロにして以前の値(EFD_SEMAPHOREなし)を返すか、カウンターを1減らして1を返します(EFD_SEMAPHOREあり)。カウンタがゼロ以外の場合、ファイル記述子はポーリング関数(selectpollepoll)から読み取り可能と見なされます。

カウンターが64ビットの制限に近い場合でも、ファイル記述子を非ブロック化すると、writeEAGAINで失敗します。カウンタがゼロの場合、readでも同じことが起こります。

15
CesarB

同期のPOSIX条件変数モデルで最大の柔軟性が必要な場合は、条件変数を公開することによってのみユーザーにイベントを通信するモジュールの作成を回避する必要があります。 (あなたは本質的にセマフォを再発明しました。)

アクティブなモジュールは、それらのインターフェースが、登録された関数を介してイベントのコールバック通知を提供するように設計する必要があります。

複数のモジュールのクライアントは、それぞれにコールバックを登録します。これらはすべて、同じミューテックスをロックし、いくつかの状態を変更し、ロックを解除し、同じ条件変数にヒットする共通の場所にルーティングできます。

この設計は、イベントに応答して実行される作業量がかなり少ない場合、おそらくコールバックのコンテキストで実行できる可能性もあります。

コールバックには、デバッグにおいてもいくつかの利点があります。コールバックの形で到着するイベントにブレークポイントを設定し、それがどのように生成されたかのコールスタックを確認できます。セマフォのウェイクアップとして到着するイベントにブレークポイントを設定した場合、または何らかのメッセージパッシングメカニズムを使用した場合、コールトレースはイベントの発生元を明らかにしません。


つまり、複数のオブジェクトでの待機をサポートするミューテックスと条件変数を使用して、独自の同期プリミティブを作成できます。これらの同期プリミティブは、アプリケーションの他の部分からは見えないように、内部的にコールバックに基づくことができます。

その要点は、スレッドが待機したいオブジェクトごとに、待機操作がそのオブジェクトとのコールバックインターフェースをキューに入れることです。オブジェクトが通知されると、オブジェクトは登録されているすべてのコールバックを呼び出します。起動されたスレッドは、すべてのコールバックインターフェイスをデキューし、それぞれのステータスフラグを調べて、どのオブジェクトがシグナルを送信したかを確認します。

4
Kaz

複数の条件変数で待機するために、興味があればLinuxに移植できるSolarisの実装があります。 WaitFor API

2
Enforcer