29

入力デバイスからのイベントをブロックして読み取っているスレッドがバックグラウンドで実行されています。アプリケーションを終了するときにスレッドを適切にクリーンアップしたいのですが、スレッドが原因でpthread_join()を実行できません。 IOがブロックされているために終了することはありません。

その状況を適切に解決するにはどうすればよいですか?ブロックを解除するには、pthread_kill(theard、SIGIO)またはpthread_kill(theard、SIGALRM)を送信する必要がありますか?それのどちらかが正しい信号でさえありますか?または、この状況を解決して、その子スレッドにブロッキング読み取りを終了させる別の方法はありますか?

私のグーグルのどれも解決策を見つけられなかったので、現在少し困惑しています。

これはLinux上で、pthreadを使用しています。

編集:SIGIOとSIGALRMを少し試してみました。シグナルハンドラーをインストールしないと、ブロッキングIOが壊れますが、コンソールにメッセージが表示されます(「I / O可能」)が、シグナルハンドラーをインストールすると、そのメッセージを回避するために、ブロッキングIOを中断しなくなったため、スレッドは終了しません。ですから、私はステップ1に戻ります。

4

13 に答える 13

18

これを行う標準的な方法はpthread_cancel、スレッドが実行したpthread_cleanup_push/popを使用して、使用しているリソースのクリーンアップを提供することです。

残念ながら、これは C++ コードでは使用できません。C++ std lib コード、またはtry {} catch()呼び出し時のスタック上のANY は、pthread_cancelプロセス全体を segvi で強制終了する可能性があります。

唯一の回避策は、I/Oを再試行する前に停止フラグを確認した場合、I/O でスレッドがブロックされている場合はSIGUSR1、停止フラグを設定して を処理することです。実際には、これは Linux で常に成功するとは限りません。理由はわかりません。pthread_kill(SIGUSR1)EINTR

ただし、サードパーティのライブラリを呼び出す必要があるかどうかについて話すのは無意味です。なぜなら、サードパーティのライブラリには、単に I/O を再起動するだけのタイトなループがある可能性が高いからEINTRです。ファイル記述子をリバース エンジニアリングして閉じることもできません。セマフォまたは他のリソースを待機している可能性があります。この場合、作業コードを書くことは単に不可能です。はい、これは完全に脳が損傷しています。C++ の例外とpthread_cancel. おそらく、これは C++ の将来のバージョンで修正される可能性があります。頑張ってください。

于 2010-09-26T22:08:01.400 に答える
15

私も、スレッドを終了するために選択またはその他の非シグナルベースの手段を使用することをお勧めします。スレッドがある理由の 1 つは、シグナルの狂気から逃れようとすることです。それは言った...

通常、pthread_kill() を SIGUSR1 または SIGUSR2 と共に使用して、スレッドにシグナルを送信します。他の提案されたシグナル (SIGTERM、SIGINT、SIGKILL) には、興味のないプロセス全体のセマンティクスがあります。

シグナルを送信したときの動作については、シグナルの処理方法に関係していると思います。ハンドラーがインストールされていない場合、そのシグナルのデフォルト アクションが適用されますが、シグナルを受信したスレッドのコンテキストで適用されます。したがって、たとえば、SIGALRM はスレッドによって「処理」されますが、その処理はプロセスの終了で構成されます。おそらく、望ましい動作ではありません。

スレッドによるシグナルの受信は、以前の回答で述べたように本当に中断できない状態でない限り、通常、EINTR を使用して読み取りから抜け出します。そうでないと、SIGALRM と SIGIO を使用した実験でプロセスが終了しなかったと思います。

あなたの読書はおそらくある種のループに入っていますか?読み取りが -1 return で終了した場合は、そのループから抜け出し、スレッドを終了します。

私の仮定をテストするためにまとめたこの非常にずさんなコードで遊ぶことができます-私は現時点でPOSIXの本から2、3タイムゾーン離れています...

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>

int global_gotsig = 0;

void *gotsig(int sig, siginfo_t *info, void *ucontext) 
{
        global_gotsig++;
        return NULL;
}

void *reader(void *arg)
{
        char buf[32];
        int i;
        int hdlsig = (int)arg;

        struct sigaction sa;
        sa.sa_handler = NULL;
        sa.sa_sigaction = gotsig;
        sa.sa_flags = SA_SIGINFO;
        sigemptyset(&sa.sa_mask);

        if (sigaction(hdlsig, &sa, NULL) < 0) {
                perror("sigaction");
                return (void *)-1;
        }
        i = read(fileno(stdin), buf, 32);
        if (i < 0) {
                perror("read");
        } else {
                printf("Read %d bytes\n", i);
        }
        return (void *)i;
}

main(int argc, char **argv)
{
        pthread_t tid1;
        void *ret;
        int i;
        int sig = SIGUSR1;

        if (argc == 2) sig = atoi(argv[1]);
        printf("Using sig %d\n", sig);

        if (pthread_create(&tid1, NULL, reader, (void *)sig)) {
                perror("pthread_create");
                exit(1);
        }
        sleep(5);
        printf("killing thread\n");
        pthread_kill(tid1, sig);
        i = pthread_join(tid1, &ret);
        if (i < 0)
                perror("pthread_join");
        else
                printf("thread returned %ld\n", (long)ret);
        printf("Got sig? %d\n", global_gotsig);

}
于 2008-10-15T08:29:21.683 に答える
9

select()特定の条件でスレッドを正常に終了するために、まれであってもタイムアウトを設定できます。私は知っています、投票は最悪です...

もう 1 つの方法は、子ごとにパイプを用意し、それをスレッドが監視するファイル記述子のリストに追加することです。子を終了させたい場合は、親からパイプにバイトを送信します。スレッドごとにパイプを犠牲にしてポーリングする必要はありません。

于 2008-10-15T06:54:36.210 に答える
6

物事が進化し、スレッド内のシグナルをより適切に処理するための新しいテクノロジーが利用できるようになったため、新しい答えが得られる可能性が非常に高い古い質問です。

Linux カーネル 2.6.22 以降、システムはsignalfd()、特定の一連の Unix シグナル (プロセスを完全に強制終了するものを除く) のファイル記述子を開くために使用できる、という新しい関数を提供します。

// defined a set of signals
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
// ... you can add more than one ...

// prevent the default signal behavior (very important)
sigprocmask(SIG_BLOCK, &set, nullptr);

// open a file descriptor using that set of Unix signals
f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);

poll()または関数を使用して、select()リッスンしていたより一般的なファイル記述子 (ソケット、ディスク上のファイルなど) に沿って信号をリッスンできます。

シグナルや他のファイル記述子を何度もチェックできるループが必要な場合、NONBLOCK は重要です (つまり、他のファイル記述子でも重要です)。

(1) タイマー、(2) ソケット、(3) パイプ、(4) Unix シグナル、(5) 通常のファイルで動作する実装があります。実際には、実際にはすべてのファイル記述子とタイマーです。

https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.cpp
https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites /snap_communicator.h

libeventなどのライブラリにも興味があるかもしれません

于 2016-04-26T03:18:42.093 に答える
6

IOをどのように待っているかによって異なります。

スレッドが "Uninterruptible IO" 状態 (上に "D" として表示) にある場合、それに対してできることはまったくありません。スレッドは通常、ページがスワップインされる (または、mmap されたファイルや共有ライブラリなどからデマンドロードされる) のを待機するなど、短時間だけこの状態に入りますが、(特に NFS サーバーの) 障害が発生する可能性があります。その状態が長く続きます。

この「D」状態から抜け出す方法は本当にありません。スレッドはシグナルに応答しません (シグナルは送信できますが、キューに入れられます)。

read() や write() などの通常の IO 関数や、select() や poll() などの待機関数であれば、シグナルは正常に配信されます。

于 2008-10-15T06:57:03.367 に答える
3

前回このような問題が発生したときに思いついた解決策の 1 つは、ブロッキング スレッドを起動するためだけに存在するファイル (パイプなど) を作成することでした。

アイデアは、メインループからファイルを作成することです(またはタイムアウトが示唆するように、スレッドごとに1つ-これにより、どのスレッドを起動するかをより細かく制御できます)。ファイル I/O でブロックしているすべてのスレッドは、操作しようとしているファイルと、メイン ループによって作成されたファイル (読み取りのメンバーとして) を使用して、select() を実行します。ファイル記述子セット)。これにより、すべての select() 呼び出しが返されるはずです。

メイン ループからこの「イベント」を処理するコードは、各スレッドに追加する必要があります。

メイン ループがすべてのスレッドをウェイクアップする必要がある場合は、ファイルに書き込むか、ファイルを閉じることができます。


これが機能するかどうかはわかりません。再構築により、試す必要がなくなったからです。

于 2008-10-15T06:53:56.540 に答える
2

あなたが言ったように、唯一の方法はシグナルを送信し、それをキャッチして適切に処理することだと思います。代替手段は、SIGTERM、SIGUSR1、SIGQUIT、SIGHUP、SIGINT などです。

入力記述子で select() を使用して、準備ができたときにのみ読み取ることもできます。たとえば、1 秒のタイムアウトで select() を使用して、そのスレッドが終了するかどうかを確認できます。

于 2008-10-15T05:47:47.250 に答える
1

誰もpthread_cancelを提案していないことに驚いています。私は最近、マルチスレッドI / Oプログラムを作成し、cancel()を呼び出し、その後、join()がうまく機能しました。

私はもともとpthread_kill()を試しましたが、テストしたシグナルでプログラム全体を終了することになりました。

于 2008-10-25T23:26:06.277 に答える
1

EINTR でループするサードパーティのライブラリでブロックしている場合は、pthread_kill をシグナル (USR1 など) と組み合わせて使用​​し、空の関数 (SIG_IGN ではない) を呼び出して、実際にファイル記述子を閉じたり置き換えたりすることを検討することをお勧めします。質問。dup2 を使用して fd を /dev/null などに置き換えると、サードパーティのライブラリが読み取りを再試行したときにファイルの終わりの結果が得られます。

最初に元のソケットを dup() することで、ソケットを実際に閉じる必要がなくなることに注意してください。

于 2009-06-16T20:28:33.907 に答える
1

スレッドが適切な時間内に確実に参加できるように、参加前に実行するスレッド関数に関連する「 kill 」関数を常に追加します。スレッドがブロッキング IO を使用する場合、システムを利用してロックを解除しようとします。たとえば、ソケットを使用する場合、ネットワークスタックがそれをきれいに終了させるkill call shutdown(2)またはclose(2)があります。

Linux のソケット実装はスレッドセーフです。

于 2008-10-15T07:43:01.760 に答える
0

シグナルとスレッドは、さまざまなマニュアル ページによると、Linux では微妙な問題です。LinuxThreads または NPTL (Linux を使用している場合) を使用していますか?

これについてはよくわかりませんが、シグナルハンドラーがプロセス全体に影響を与えると思うので、プロセス全体を終了するか、すべてを続行します。

時限選択またはポーリングを使用し、グローバル フラグを設定してスレッドを終了する必要があります。

于 2008-10-15T07:57:20.843 に答える
0
struct pollfd pfd;
pfd.fd = socket;
pfd.events = POLLIN | POLLHUP | POLLERR;
pthread_lock(&lock);
while(thread_alive)
{
    int ret = poll(&pfd, 1, 100);
    if(ret == 1)
    {
        //handle IO
    }
    else
    {
         pthread_cond_timedwait(&lock, &cond, 100);
     }
}
pthread_unlock(&lock);

thread_alive は、スレッドを強制終了するシグナルと組み合わせて使用​​できるスレッド固有の変数です。

ハンドル IO セクションについては、O_NOBLOCK オプションで open を使用したことを確認する必要があります。または、ソケットの場合は、MSG_NOWAIT?? を設定できる同様のフラグがあります。他のfdsについてはわかりません

于 2008-10-15T16:07:22.663 に答える
0

最もクリーンなアプローチは、継続するためにループ内で条件変数を使用するスレッドを持つことだと思います。

I/O イベントが発生すると、条件が通知されます。

メイン スレッドは、ループ述語を false に変更しながら条件を通知するだけで済みます。

何かのようなもの:

while (!_finished)
{
    pthread_cond_wait(&cond);
    handleio();
}
cleanup();

シグナルを適切に処理するために条件変数を覚えておいてください。「偽のウェイクアップ」などを持つことができます。したがって、独自の関数を cond_wait 関数にラップします。

于 2008-10-15T13:58:03.283 に答える