Ich würde auch empfehlen, eine Auswahl oder ein anderes nicht signalbasiertes Mittel zum Beenden Ihres Threads zu verwenden. Einer der Gründe, warum wir Threads haben, ist der Versuch, dem Signalwahn zu entkommen. Das heißt...
Im Allgemeinen verwendet man pthread_kill() mit SIGUSR1 oder SIGUSR2, um ein Signal an den Thread zu senden. Die anderen vorgeschlagenen Signale – SIGTERM, SIGINT, SIGKILL – haben eine prozessweite Semantik, an der Sie möglicherweise nicht interessiert sind.
Was das Verhalten beim Senden des Signals betrifft, so vermute ich, dass es damit zu tun hat, wie Sie mit dem Signal umgegangen sind. Wenn Sie keinen Handler installiert haben, wird die Standardaktion dieses Signals angewendet, jedoch im Kontext des Threads, der das Signal empfangen hat. So würde zum Beispiel SIGALRM von Ihrem Thread "behandelt", aber die Behandlung würde darin bestehen, den Prozess zu beenden - wahrscheinlich nicht das gewünschte Verhalten.
Der Empfang eines Signals durch den Thread wird es im Allgemeinen mit EINTR aus einem Lesevorgang herausbrechen, es sei denn, es befindet sich wirklich in diesem unterbrechungsfreien Zustand, wie in einer früheren Antwort erwähnt. Aber ich denke, das ist es nicht, oder Ihre Experimente mit SIGALRM und SIGIO hätten den Prozess nicht beendet.
Befindet sich Ihr Lesevorgang vielleicht in einer Art Schleife? Wenn der Lesevorgang mit -1 return endet, brechen Sie aus dieser Schleife aus und beenden Sie den Thread.
Sie können mit diesem sehr schlampigen Code spielen, den ich zusammengestellt habe, um meine Annahmen zu testen - ich bin im Moment ein paar Zeitzonen von meinen POSIX-Büchern entfernt ...
#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);
}
Alte Frage, die sehr gut eine neue Antwort erhalten könnte, wenn sich die Dinge weiterentwickelt haben und eine neue Technologie jetzt besser verfügbar ist Signale in Threads behandeln.
Seit dem Linux-Kernel 2.6.22 bietet das System eine neue Funktion namens signalfd()
die verwendet werden kann, um einen Dateideskriptor für einen bestimmten Satz von Unix-Signalen zu öffnen (außer denen, die einen Prozess direkt beenden).
// 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);
Jetzt können Sie die poll()
verwenden oder select()
Funktionen zum Abhören des Signals zusammen mit dem üblicheren Dateideskriptor (Socket, Datei auf der Festplatte usw.), den Sie abgehört haben.
Der NONBLOCK ist wichtig, wenn Sie eine Schleife wollen, die Signale und andere Dateideskriptoren immer wieder überprüfen kann (d.h. es ist auch wichtig für Ihre anderen Dateideskriptoren).
Ich habe eine solche Implementierung, die mit (1) Timern, (2) Sockets, (3) Pipes, (4) Unix-Signalen, (5) regulären Dateien funktioniert. Eigentlich wirklich jeder Dateideskriptor plus Timer.
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
Sie könnten auch an Bibliotheken wie libevent
interessiert sein
Der kanonische Weg, dies zu tun, ist mit pthread_cancel
, wo der Thread pthread_cleanup_push
ausgeführt hat /pop
um alle verwendeten Ressourcen zu bereinigen.
Leider kann dies niemals in C++-Code verwendet werden. Beliebiger C++-Standardbibliothekscode oder JEDER try {} catch()
auf dem aufrufenden Stack zum Zeitpunkt pthread_cancel
wird potenziell segvi Ihren gesamten Prozess beenden.
Die einzige Problemumgehung besteht darin, SIGUSR1
zu handhaben , Setzen eines Stop-Flags, pthread_kill(SIGUSR1)
, dann überall dort, wo der Thread bei I/O blockiert ist, wenn Sie EINTR
erhalten Überprüfen Sie das Stopp-Flag, bevor Sie die E/A erneut versuchen. In der Praxis gelingt das unter Linux nicht immer, keine Ahnung warum.
Aber auf jeden Fall ist es sinnlos, darüber zu sprechen, ob Sie eine Bibliothek von Drittanbietern anrufen müssen, da diese höchstwahrscheinlich eine enge Schleife haben, die die E/A einfach auf EINTR
neu startet . Das Zurückentwickeln ihres Dateideskriptors zum Schließen wird auch nicht ausreichen - sie könnten auf ein Semaphor oder eine andere Ressource warten. In diesem Fall ist es einfach unmöglich, funktionierenden Code zu schreiben, Punkt. Ja, das ist absolut hirngeschädigt. Sprechen Sie mit den Leuten, die C++-Ausnahmen und pthread_cancel
entworfen haben . Angeblich wird dies in einer zukünftigen Version von C++ behoben. Viel Glück dabei.
Ihr select()
könnte ein Timeout haben, auch wenn es selten vorkommt, um den Thread unter einer bestimmten Bedingung ordnungsgemäß zu beenden. Ich weiß, Umfragen sind scheiße...
Eine andere Alternative besteht darin, für jedes untergeordnete Element eine Pipe zu haben und diese der Liste der Dateideskriptoren hinzuzufügen, die vom Thread überwacht werden. Senden Sie ein Byte vom übergeordneten Element an die Pipe, wenn das untergeordnete Element beendet werden soll. Kein Polling auf Kosten einer Pipe pro Thread.