7

次のプロパティでスレッドを実行しようとすると、この問題が発生し続けます。

  1. 無限ループで実行され、ネットワークやデバイスからのデータなどの外部リソースをチェックします。
  2. そのリソースから更新を迅速に取得し、
  3. 頼まれたらすぐに退出し、
  4. CPU を効率的に使用します。

最初のアプローチ

これについて私が見た1つの解決策は、次のようなものです。

void class::run()
{
    while(!exit_flag)
    {
        if (resource_ready)
            use_resource();
    }
}

これはポイント 1、2、および 3 を満たしますが、ビジーな待機ループであるため、100% の CPU を使用します。

2 番目のアプローチ

これを修正する可能性があるのは、sleep ステートメントを次の場所に置くことです。

void class::run()
{
    while(!exit_flag)
    {
        if (resource_ready)
            use_resource();
        else
            sleep(a_short_while);
    }
}

CPU に負荷をかけないので、1 と 4 に対処しますがa_short_while、リソースの準備が整ったときや終了を求められたときに、不必要に待機する可能性があります。

3 番目のアプローチ

3 番目のオプションは、リソースでブロッキング読み取りを行うことです。

void class::run()
{
    while(!exit_flag)
    {
        obtain_resource();
        use_resource();
    }
}

これは 1、2、および 4 をエレガントに満たしますが、リソースが使用可能にならない場合にスレッドを終了するように要求することはできません。

質問

CPU 使用率と応答性の間のトレードオフが達成できる限り、最善のアプローチは短いスリープの 2 番目の方法のようです。ただし、これはまだ最適ではないようで、私にはエレガントではありません。これは、解決すべき一般的な問題のようです。それを解決するよりエレガントな方法はありますか?これらの 4 つの要件すべてに対応できるアプローチはありますか?

4

7 に答える 7

8

これは、スレッドがアクセスしているリソースの詳細に依存しますが、基本的に最小限のレイテンシで効率的に行うには、リソースは割り込み可能なブロッキング待機を行うための API を提供する必要があります。

POSIX システムでは、使用しているリソースがファイルまたはファイル記述子 (ソケットを含む) である場合、select(2)またはシステム コールを使用してこれを行うことができます。poll(2)待機を横取りできるようにするには、書き込み可能なダミー パイプも作成します。

たとえば、ファイル記述子またはソケットの準備が整うまで、またはコードが中断されるまで待機する方法を次に示します。

// Dummy pipe used for sending interrupt message
int interrupt_pipe[2];
int should_exit = 0;

void class::run()
{
    // Set up the interrupt pipe
    if (pipe(interrupt_pipe) != 0)
        ;  // Handle error

    int fd = ...;  // File descriptor or socket etc.
    while (!should_exit)
    {
        // Set up a file descriptor set with fd and the read end of the dummy
        // pipe in it
        fd_set fds;
        FD_CLR(&fds);
        FD_SET(fd, &fds);
        FD_SET(interrupt_pipe[1], &fds);
        int maxfd = max(fd, interrupt_pipe[1]);

        // Wait until one of the file descriptors is ready to be read
        int num_ready = select(maxfd + 1, &fds, NULL, NULL, NULL);
        if (num_ready == -1)
            ; // Handle error

        if (FD_ISSET(fd, &fds))
        {
            // fd can now be read/recv'ed from without blocking
            read(fd, ...);
        }
    }
}

void class::interrupt()
{
    should_exit = 1;

    // Send a dummy message to the pipe to wake up the select() call
    char msg = 0;
    write(interrupt_pipe[0], &msg, 1);
}

class::~class()
{
    // Clean up pipe etc.
    close(interrupt_pipe[0]);
    close(interrupt_pipe[1]);
}

Windows を使用している場合、select()関数はソケットに対してのみ機能しますが、ソケットに対してのみ機能するため、use をインストールWaitForMultipleObjectsしてリソース ハンドルとイベント ハンドルを待機する必要があります。例えば:

// Event used for sending interrupt message
HANDLE interrupt_event;
int should_exit = 0;

void class::run()
{
    // Set up the interrupt event as an auto-reset event
    interrupt_event = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (interrupt_event == NULL)
        ;  // Handle error

    HANDLE resource = ...;  // File or resource handle etc.
    while (!should_exit)
    {
        // Wait until one of the handles becomes signaled
        HANDLE handles[2] = {resource, interrupt_event};
        int which_ready = WaitForMultipleObjects(2, handles, FALSE, INFINITE);    
        if (which_ready == WAIT_FAILED)
            ; // Handle error
        else if (which_ready == WAIT_OBJECT_0))
        {
            // resource can now be read from without blocking
            ReadFile(resource, ...);
        }
    }
}

void class::interrupt()
{
    // Signal the event to wake up the waiting thread
    should_exit = 1;
    SetEvent(interrupt_event);
}

class::~class()
{
    // Clean up event etc.
    CloseHandle(interrupt_event);
}
于 2013-10-10T01:02:28.570 に答える
3

obtain_ressource()関数がタイムアウト値をサポートしている場合、効率的なソリューションが得られます。

while(!exit_flag)
{
    obtain_resource_with_timeout(a_short_while);
    if (resource_ready)
        use_resource();
}

sleep()これにより、とコールが効果的に結合されobtain_ressurce()ます。

于 2013-10-10T01:01:39.767 に答える
2

のマンページをnanosleepチェックしてください:

シグナルによって中断されたために nanosleep() 関数が戻る場合、この関数は -1 の値を返し、中断を示すために errno を設定します。

つまり、シグナルを送信することで、スリープ状態のスレッドに割り込むことができます (sleepマンページにも同様のことが書かれています)。これは、2 番目のアプローチを使用し、割り込みを使用して、スレッドがスリープしている場合にスレッドをすぐに起動できることを意味します。

于 2013-10-10T01:13:06.523 に答える
1

Gang of Four Observer パターンを使用します。

http://home.comcast.net/~codewrangler/tech_info/patterns_code.html#Observer

コールバック、ブロックしないでください。

于 2013-10-10T01:00:33.513 に答える
0

ここでセルフパイプトリックが使えます。 http://cr.yp.to/docs/selfpipe.html ファイル記述子からデータを読み取っていると仮定します。

パイプ入力と関心のあるリソースで読みやすくするために、パイプと select() を作成します。次に、データがリソースに到着すると、スレッドが起動して処理を行います。そうでなければ眠る。スレッドを終了するには、シグナルを送信し、シグナル ハンドラーでパイプに何かを書き込みます (要点を説明するための NULL のような、関心のあるリソースからは決して来ない何かを言います)。select 呼び出しが返され、入力を読み取るスレッドは、ポイズン ピルを取得したことを認識し、終了して pthread_exit() を呼び出します。

編集:より良い方法は、データがパイプに送信されたことを確認することです。したがって、そのパイプに送信された値をチェックするのではなく、単に終了します。

于 2013-10-10T03:58:37.787 に答える
0

上記の(少なくとも、ほとんどの)アプローチは次のことを行います。スレッドが作成され、リソースの書き込みがブロックされ、削除されます。

効率が心配な場合、これは IO を待機するときに最適な方法ではありません。少なくとも Windows では、ユーザー モードで約 1 MB のメモリを割り当て、一部はカーネルで 1 つの追加スレッド用に割り当てます。そのようなリソースがたくさんある場合はどうなりますか? 待機中のスレッドが多数あると、コンテキスト スイッチが増加し、プログラムの速度が低下します。リソースが利用可能になるまでに時間がかかり、多くのリクエストが行われた場合はどうなりますか? 大量の待機中のスレッドが発生する可能性があります。

さて、それに対する解決策(これもWindowsですが、他のOSにも同様のものがあるはずです)は、スレッドプール(Windowsが提供するもの)を使用することです。Windows では、これは限られた量のスレッドを作成するだけでなく、スレッドが IO を待機していることを検出し、そこからスレッドを妨害し、待機中に他の操作に再利用します。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms686766(v=vs.85).aspxを参照してください。

また、IO を待機しているときにスレッドをあきらめる機能をまだ持っている、よりきめ細かい制御ビットについては、IO 完了ポートを参照してください (とにかく内部でスレッドプールを使用すると思います): http://msdn.microsoft.com/en-us/ライブラリ/ウィンドウ/デスクトップ/aa365198(v=vs.85).aspx

于 2013-10-10T06:50:38.387 に答える