私も、スレッドを終了するために選択またはその他の非シグナルベースの手段を使用することをお勧めします。スレッドがある理由の 1 つは、シグナルの狂気から逃れようとすることです。そうは言っても...
通常、pthread_kill() を SIGUSR1 または SIGUSR2 と共に使用して、スレッドにシグナルを送信します。他の提案されたシグナル (SIGTERM、SIGINT、SIGKILL) には、興味のないプロセス全体のセマンティクスがあります。
シグナルを送信したときの動作については、シグナルの処理方法に関係していると思います。ハンドラーがインストールされていない場合、そのシグナルのデフォルト アクションが適用されますが、シグナルを受信したスレッドのコンテキストで適用されます。したがって、たとえば、SIGALRM はスレッドによって「処理」されますが、その処理はプロセスの終了で構成されます。おそらく、望ましい動作ではありません。
スレッドによるシグナルの受信は、以前の回答で述べたように本当に中断できない状態でない限り、通常、EINTR を使用して読み取りから抜け出します。そうでないと、SIGALRM と SIGIO を使用した実験でプロセスが終了しなかったと思います。
あなたの読書はおそらくある種のループに入っていますか?読み取りが -1 リターンで終了した場合、そのループから抜け出し、スレッドを終了します。
私の仮定をテストするためにまとめたこの非常にずさんなコードで遊んでみてください。私は現在、私の POSIX 本からタイムゾーンが 2 つ離れています...
#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);
}
物事が進化し、新しいテクノロジーがより良いに利用できるようになったため、新しい答えが得られる可能性が非常に高い古い質問 スレッドでシグナルを処理します。
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 などのライブラリにも興味があるかもしれません
これを行う標準的な方法は、 pthread_cancel
を使用することです 、スレッドが実行した場所 pthread_cleanup_push
/pop
使用しているすべてのリソースのクリーンアップを提供します。
残念ながら、これは C++ コードでは使用できません。任意の C++ std lib コード、または任意の try {} catch()
pthread_cancel
時の呼び出しスタック segvi によってプロセス全体が強制終了される可能性があります。
唯一の回避策は SIGUSR1
を処理することです 、停止フラグの設定、pthread_kill(SIGUSR1)
EINTR
を取得した場合、I/O でスレッドがブロックされている場所 I/O を再試行する前に停止フラグを確認してください。実際には、これは Linux で常に成功するとは限りません。理由はわかりません。
しかし、いずれにしても、サードパーティのライブラリを呼び出す必要があるかどうかについて話すのは無意味です。なぜなら、それらは EINTR
で I/O を再起動するだけのタイトなループを持っている可能性が高いからです。 .ファイル記述子をリバース エンジニアリングして閉じることもできません。セマフォまたは他のリソースを待機している可能性があります。この場合、作業コードを書くことは単に不可能です。はい、これは完全に脳が損傷しています。 C++ 例外と pthread_cancel
を設計した人たちと話してください .おそらく、これは C++ の将来のバージョンで修正される可能性があります。頑張ってください。
あなたの select()
特定の条件でスレッドを正常に終了するために、まれであってもタイムアウトを設定できます。私は知っています、投票は最悪です...
もう 1 つの方法は、子ごとにパイプを用意し、それをスレッドが監視するファイル記述子のリストに追加することです。子を終了させたい場合は、親からパイプにバイトを送信します。スレッドごとにパイプを犠牲にしてポーリングする必要はありません。