web-dev-qa-db-ja.com

スレッド化されたC ++プログラムをきれいに終了する方法は?

プログラムで複数のスレッドを作成しています。押すと Ctrl-C、シグナルハンドラが呼び出されます。シグナルハンドラ内に、最後にexit(0)を配置しました。問題は、プログラムが安全に終了することもありますが、それ以外の場合は、実行時エラーが表示されることです

abort() has been called

それでは、エラーを回避するための可能な解決策は何でしょうか?

31
Rishav Jain

通常の方法は、すべてのスレッド(メインスレッドを含む)によってチェックされるatomicフラグ(std::atomic<bool>など)を設定することです。設定すると、サブスレッドが終了し、メインスレッドがサブスレッドをjoin開始します。 Thenきれいに終了できます。

スレッドにstd::threadを使用すると、クラッシュが発生する可能性があります。 std::threadオブジェクトを破棄する前に、スレッドをjoinする必要があります。

30

他の人は、シグナルハンドラーに_std::atomic<bool>_を設定させ、他のすべてのスレッドにその値を定期的にチェックさせて、いつ終了するかを知らせることを言及しています。

このアプローチは、他のすべてのスレッドが適切な頻度でとにかく定期的に起動している限り、うまく機能します。

ただし、1つ以上のスレッドが純粋にイベント駆動型である場合、完全に満足できるものではありません。ただし、イベント駆動型プログラムでは、スレッドは、実行する作業がある場合にのみ起動することになっています。一度に数日または数週間眠ります。単にアトミックブールフラグをポーリングするために(非常に多く)ミリ秒ごとにウェイクアップするように強制された場合、これにより、非常にCPU効率の高いプログラムのCPU効率が大幅に低下します。 24時間365日。これは、CPUが省電力モードに移行するのを妨げる可能性があるため、バッテリー寿命を節約しようとする場合、特に問題になる可能性があります。

ポーリングを回避する別のアプローチは次のとおりです。

  1. 起動時に、メインスレッドにfd-pipeまたはsocket-pairを作成させます( pipe() または socketpair() を呼び出して)
  2. メインスレッド(または、おそらく他の責任スレッド)に、受信ソケットを読み取り可能select() fd_setに含める(またはpoll()またはその他の待機のために同様のアクションを実行させる)スレッドがブロックするIO関数)
  3. シグナルハンドラが実行されたら、送信ソケットに1バイト(任意のバイト、何でも構いません)を書き込むようにします。
  4. これにより、メインスレッドのselect()呼び出しがすぐに戻り、FD_ISSET(receivingSocket)は受信したバイトのためにtrueを示します。
  5. その時点で、メインスレッドはプロセスが終了する時間であると認識しているため、すべての子スレッドにシャットダウンを開始するよう指示することができます(便利なメカニズム、アトミックブール値またはパイプなど)。
  6. すべての子スレッドにシャットダウンを開始するように指示した後、メインスレッドは各子スレッドでjoin()を呼び出す必要があります。これにより、すべての子スレッドが実際になくなったことを保証できますbeforemain()は戻ります。 (これは、競合状態のリスクがあるために必要です-たとえば、post-main()クリーンアップコードは、まだ実行中の子スレッドがリソースを使用しているときにリソースを解放し、クラッシュにつながる場合があります)
14
Jeremy Friesner

最初に受け入れる必要があるのは、スレッド化が難しいということです。

「スレッドを使用するプログラム」は「メモリを使用するプログラム」とほぼ同じくらい一般的であり、あなたの質問は「メモリを使用するプログラムでメモリを破損しない方法」に似ています。

スレッドの問題を処理する方法は、スレッドの使用方法とスレッドの動作を制限することです。

スレッドシステムがデータフローネットワークに組み込まれた小さな操作の束である場合、操作が大きすぎる場合は小さな操作に分割され、システムでチェックポイントを実行するという暗黙の保証により、シャットダウンは非常に異なるように見えます外部のDLLをロードして、1秒から10時間まで無限の長さで実行するスレッドがある場合よりも。

C++のほとんどのものと同様に、問題の解決は、所有権、制御、および(最後の手段として)ハッキングに関するものです。

C++のデータと同様に、すべてのスレッドを所有する必要があります。スレッドの所有者は、そのスレッドを大幅に制御し、アプリケーションがシャットダウンしていることを通知できる必要があります。シャットダウンメカニズムは堅牢でテスト済みで、理想的には他のメカニズム(投機的タスクの早期中止など)に接続する必要があります。

Exit(0)を呼び出しているという事実は悪い兆候です。 main実行スレッドにクリーンなシャットダウンパスがないことを意味します。そこから始めましょう。割り込みハンドラーは、シャットダウンを開始するスレッドをmainに通知し、メインスレッドを正常にシャットダウンする必要があります。すべてのスタックフレームを巻き戻し、データをクリーンアップする必要があります。

次に、クリーンで高速なシャットダウンを可能にする同じ種類のロジックを、スレッド化されたコードにも適用する必要があります。

条件変数/アトミックブール値と同じくらい簡単だとあなたが言っている人は誰でも、ポーリングはあなたに手形を売っています。運がよければ簡単な場合にのみ機能し、確実に機能するかどうかを判断するのは非常に困難です。

一部のプログラマーの回答に加えて、コメントセクションの説明に関連して、スレッドの終了をatomicタイプとして制御するフラグを作成する必要があります。

次の場合を検討してください。

bool done = false;
void pending_thread()
{
    while(!done)
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

ここで、ワーカースレッドはmainスレッドにすることもでき、doneはスレッドのフラグを終了しますが、保留中のスレッドは、終了する前に作業スレッドによって特定のデータを処理する必要があります。

この例には、競合状態と未定義の動作があり、実際の問題を実際に見つけるのは非常に困難です。

std::automicを使用した修正バージョン:

std::atomic<bool> done(false);
void pending_thread()
{
    while(!done.load())
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

競合状態やUBを気にせずにスレッドを終了できます。

3
HMD