8

POSIXタイマー(timer_create())を使用するプログラムがあります。基本的に、プログラムはタイマーを設定し、長い(場合によっては無限の)計算の実行を開始します。タイマーの期限が切れてシグナルハンドラーが呼び出されると、ハンドラーは計算された最高の結果を出力し、プログラムを終了します。

OpenMPを使用して計算を並行して実行することを検討します。これにより、計算が高速化されるはずです。

pthreadには、たとえばスレッドなどのシグナルマスクを設定するための特別な関数があります。OpenMPはそのような制御を提供しますか、それともOpenMPが作成するスレッドのいずれかにシグナルを配信できるという事実を受け入れる必要がありますか?

また、現在コードの並列セクションにいてハンドラーが呼び出された場合でも、アプリケーションを安全に強制終了でき(exit(0);)、OpenMPロックのロックなどを実行できますか?

4

2 に答える 2

2

OpenMP 3.1標準は、シグナルについては何も述べていません。

私が知っているように、Linux / UNIXでの一般的なOpenMP実装はすべてpthreadに基づいているため、OpenMPスレッドはpthreadのスレッドです。また、pthreadとシグナルの一般的なルールが適用されます。

OpenMPはそのような制御を提供しますか

特定のコントロールはありません。ただし、pthreadのコントロールを使用してみることができます。唯一の問題は、使用されているOpenMPスレッドの量と、制御ステートメントを配置する場所を知ることです。

シグナルは、OpenMPが作成するスレッドのいずれかに配信できますか?

デフォルトでは、はい、それは任意のスレッドに配信されます。

私のハンドラーは呼び出されます、

シグナルハンドラに関する通常のルールは引き続き適用されます。シグナルハンドラで許可されている関数は、http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html(ページの最後)にリストされています。

そしてprintf許可されていません(writeです)。シグナルの時点でprintfがどのスレッドでも使用されていないことがわかっている場合は、printfを使用できます(たとえば、並列領域にprintfがない場合)。

それでもアプリケーションを安全に強制終了できますか(exit(0);)

はい、できます。ハンドラーから許可されますabort()_exit()

Linux / Unixは、いずれかのスレッドがexitまたはを実行すると、すべてのスレッドを終了しabortます。

そして、OpenMPロックをロックするようなことをしますか?

すべきではありませんが、シグナルハンドラの実行時にこのロックがロックされないことがわかっている場合は、これを試すことができます。

!! アップデート

OpenMP http://www.cs.colostate.edu/~cs675/OpenMPvsThreads.pdf(「OpenMPとC / C ++でのスレッド化」)にシグナリングを採用する例があります。つまり、ハンドラーにフラグを設定し、N回目のループ反復ごとにすべてのスレッドにこのフラグのチェックを追加します。

信号ベースの例外メカニズムを並列領域に適応させる

C / C ++アプリケーションでFortranアプリケーションよりも多く発生するのは、プログラムが高度なユーザーインターフェイスを使用していることです。Genehunterは、ユーザーがcontrol-Cを押して、ある家系図の計算を中断し、病気に関する臨床データベースの次の家系図に進むことができる簡単な例です。シリアルバージョンでは、シグナルハンドラー、setjump、およびlongjumpを含むC ++のような例外メカニズムによって早期終了が処理されます。OpenMPでは、非構造化制御フローが並列構造の境界を越えることはできません。OpenMPバージョンでは、割り込みハンドラーをポーリングメカニズムに変更することで、例外処理を変更しました。control-Cシグナルをキャッチするスレッドは、共有フラグを設定します。すべてのスレッドは、ルーチンhas_hit_interrupt()を呼び出してループの開始時にフラグをチェックし、設定されている場合は反復をスキップします。ループが終了すると、マスターはフラグをチェックし、ロングジャンプを簡単に実行して例外的な終了を完了することができます(図1を参照)。

于 2011-11-16T18:44:59.310 に答える
2

これは少し遅いですが、うまくいけば、このサンプルコードが同様の立場にいる他の人を助けるでしょう!


osgxが述べたように、OpenMPはシグナルの問題について沈黙していますが、OpenMPはPOSIXシステム上のpthreadで実装されることが多いため、pthreadシグナルアプローチを使用できます。

OpenMPを使用した大量の計算の場合、計算を実際に安全に停止できる場所はごくわずかである可能性があります。したがって、時期尚早の結果を取得したい場合は、同期信号処理を使用してこれを安全に行うことができます。追加の利点は、これにより特定のOpenMPスレッドからのシグナルを受け入れることができることです(以下のサンプルコードでは、マスタースレッドを選択します)。信号をキャッチすると、計算を停止する必要があることを示すフラグを設定するだけです。次に、各スレッドは、都合のよいときにこのフラグを定期的にチェックしてから、ワークロードのシェアをまとめる必要があります。

この同期アプローチを使用することにより、アルゴリズムへの変更を最小限に抑えて、計算を正常に終了できます。一方、各スレッドの現在の動作状態をコヒーレントな結果に照合することは困難である可能性があるため、必要に応じたシグナルハンドラーアプローチは適切でない場合があります。ただし、同期アプローチの欠点の1つは、計算が停止するまでにかなりの時間がかかる可能性があることです。

信号チェック装置は、次の3つの部分で構成されています。

  • 関連する信号をブロックします。omp parallelこれは、各OpenMPスレッド(pthread)がこれと同じブロッキング動作を継承するように、リージョン外で実行する必要があります。
  • マスタースレッドからの目的のシグナルをポーリングします。これに使用できますsigtimedwaitが、一部のシステム(MacOSなど)はこれをサポートしていません。より移植性の高い方法sigpendingとして、ブロックされた信号をポーリングし、ブロックされた信号が期待どおりであることを再確認してから、同期を使用して受け入れることができますsigwait(プログラムの他の部分が競合状態を作成していない限り、ここにすぐに戻るはずです)。 )。最終的に関連するフラグを設定しました。
  • 最後にシグナルマスクを削除する必要があります(オプションで、シグナルの最終チェックを1回行います)。

パフォーマンスに関する重要な考慮事項と注意事項がいくつかあります。

  • 各内部ループの反復が小さいと仮定すると、シグナルチェックシステムコールの実行にはコストがかかります。サンプルコードでは、1000万(スレッドあたり)の反復ごとにのみシグナルをチェックします。これは、おそらく数秒の壁時間に相当します。
  • omp forループを1から分割することはできないため、残りの反復でスピンするか、より基本的なOpenMPプリミティブを使用してループを書き直す必要があります。通常のループ(外側の並列ループの内側のループなど)は、問題なく分割できます。
  • マスタースレッドのみがシグナルをチェックできる場合、マスタースレッドが他のスレッドよりもかなり前に終了するプログラムで問題が発生する可能性があります。このシナリオでは、これらの他のスレッドは中断できません。これに対処するには、各スレッドがワークロードを完了するときにシグナルチェックの「バトンを渡す」か、他のすべてのスレッドが完了するまでマスタースレッドの実行とポーリングを強制することができます2
  • NUMA HPCなどの一部のアーキテクチャでは、「グローバル」信号フラグをチェックする時間が非常に長くなる可能性があるため、フラグをチェックまたは操作するタイミングと場所を決定するときは注意してください。たとえば、スピンループセクションの場合、フラグがtrueになったときにローカルにキャッシュしたい場合があります。

サンプルコードは次のとおりです。

#include <signal.h>

void calculate() {
    _Bool signalled = false;
    int sigcaught;
    size_t steps_tot = 0;

    // block signals of interest (SIGINT and SIGTERM here)
    sigset_t oldmask, newmask, sigpend;
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    sigaddset(&newmask, SIGTERM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);

    #pragma omp parallel
    {
        int rank = omp_get_thread_num();
        size_t steps = 0;

        // keep improving result forever, unless signalled
        while (!signalled) {
            #pragma omp for
            for (size_t i = 0; i < 10000; i++) {
                // we can't break from an omp for loop...
                // instead, spin away the rest of the iterations
                if (signalled) continue;

                for (size_t j = 0; j < 1000000; j++, steps++) {
                    // ***
                    // heavy computation...
                    // ***

                    // check for signal every 10 million steps
                    if (steps % 10000000 == 0) {

                        // master thread; poll for signal
                        if (rank == 0) {
                            sigpending(&sigpend);
                            if (sigismember(&sigpend, SIGINT) || sigismember(&sigpend, SIGTERM)) {
                                if (sigwait(&newmask, &sigcaught) == 0) {
                                    printf("Interrupted by %d...\n", sigcaught);
                                    signalled = true;
                                }
                            }
                        }

                        // all threads; stop computing
                        if (signalled) break;
                    }
                }
            }
        }

        #pragma omp atomic
        steps_tot += steps;
    }

    printf("The result is ... after %zu steps\n", steps_tot);

    // optional cleanup
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
}

C ++を使用している場合は、次のクラスが役立つ場合があります...

#include <signal.h>
#include <vector>

class Unterminable {
    sigset_t oldmask, newmask;
    std::vector<int> signals;

public:
    Unterminable(std::vector<int> signals) : signals(signals) {
        sigemptyset(&newmask);
        for (int signal : signals)
            sigaddset(&newmask, signal);
        sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    }

    Unterminable() : Unterminable({SIGINT, SIGTERM}) {}

    // this can be made more efficient by using sigandset,
    // but sigandset is not particularly portable
    int poll() {
        sigset_t sigpend;
        sigpending(&sigpend);
        for (int signal : signals) {
            if (sigismember(&sigpend, signal)) {
                int sigret;
                if (sigwait(&newmask, &sigret) == 0)
                    return sigret;
                break;
            }
        }
        return -1;
    }

    ~Unterminable() {
        sigprocmask(SIG_SETMASK, &oldmask, NULL);
    }
};

次に、のブロッキング部分をcalculate()、に置き換えUnterminable unterm();、信号チェック部分を。に置き換えることができif ((sigcaught = unterm.poll()) > 0) {...}ます。信号のブロック解除はunterm、スコープ外になると自動的に実行されます。


1これは厳密には真実ではありません。OpenMPは、キャンセルポイントの形式で「並列ブレーク」を実行するための限定的なサポートをサポートします。並列ループでキャンセルポイントを使用する場合は、キャンセル時に計算データの一貫性を確保するために、暗黙のキャンセルポイントがどこにあるかを正確に把握してください。

2個人的には、forループを完了したスレッドの数をカウントし、マスタースレッドがシグナルをキャッチせずにループを完了した場合、シグナルをキャッチするか、すべてのスレッドがループを完了するまで、シグナルのポーリングを続けます。これを行うには、必ずforループをマークしてくださいnowait

于 2019-05-01T12:22:24.327 に答える