Také bych doporučil použít výběr nebo jiný způsob ukončení vašeho vlákna, který není založen na signálu. Jedním z důvodů, proč máme vlákna, je pokusit se dostat pryč od signálního šílenství. To znamená...
Obecně se používá pthread_kill() se SIGUSR1 nebo SIGUSR2 k odeslání signálu do vlákna. Ostatní navrhované signály – SIGTERM, SIGINT, SIGKILL – mají sémantiku celého procesu, která vás nemusí zajímat.
Pokud jde o chování, když jste vyslali signál, odhaduji, že to souvisí s tím, jak jste se signálem zacházeli. Pokud nemáte nainstalovaný žádný obslužný program, použije se výchozí akce tohoto signálu, ale v kontextu vlákna, které signál přijalo. Takže například SIGALRM by byl "zpracován" vaším vláknem, ale zpracování by sestávalo z ukončení procesu - pravděpodobně to není požadované chování.
Přijetí signálu vláknem jej obecně přeruší ze čtení pomocí EINTR, pokud není skutečně v nepřerušitelném stavu, jak je uvedeno v dřívější odpovědi. Ale myslím, že tomu tak není, jinak by vaše experimenty se SIGALRM a SIGIO proces neukončily.
Je vaše čtení možná v nějaké smyčce? Pokud čtení skončí s návratem -1, opusťte tuto smyčku a ukončete vlákno.
Můžete si hrát s tímto velmi nedbalým kódem, který jsem sestavil, abych otestoval své předpoklady – momentálně jsem pár časových pásem od svých knih POSIX...
#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);
}
Stará otázka, která by mohla dostat novou odpověď, protože věci se vyvíjely a nová technologie je nyní k dispozici lepší zpracovávat signály ve vláknech.
Od linuxového jádra 2.6.22 systém nabízí novou funkci nazvanou signalfd()
který lze použít k otevření deskriptoru souboru pro danou sadu unixových signálů (mimo těch, které přímo zabíjejí proces.)
// 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);
Nyní můžete použít poll()
nebo select()
slouží k naslouchání signálu podle obvyklejšího deskriptoru souboru (zásuvka, soubor na disku atd.), na kterém jste poslouchali.
NONBLOCK je důležitý, pokud chcete smyčku, která dokáže kontrolovat signály a další deskriptory souborů znovu a znovu (tj. je důležitá i na vašem dalším deskriptoru souboru).
Mám takovou implementaci, která pracuje s (1) časovači, (2) zásuvkami, (3) rourami, (4) unixovými signály, (5) běžnými soubory. Vlastně opravdu jakýkoli deskriptor souboru plus časovače.
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
Také by vás mohly zajímat knihovny jako libevent
Kanonický způsob, jak toho dosáhnout, je pthread_cancel
, kde vlákno dosáhlo pthread_cleanup_push
/pop
zajistit vyčištění všech zdrojů, které používá.
Bohužel to NELZE použít v kódu C++. Jakýkoli kód C++ std lib nebo JAKÝKOLI try {} catch()
na zásobníku volání v době pthread_cancel
potenciálně Segvi zabije celý váš proces.
Jediným řešením je zpracovat SIGUSR1
, nastavení příznaku zastavení, pthread_kill(SIGUSR1)
, pak kdekoli je vlákno blokováno na I/O, pokud dostanete EINTR
před dalším pokusem o vstup/výstup zkontrolujte příznak zastavení. V praxi to na Linuxu vždy neuspěje, nevím proč.
Ale v každém případě je zbytečné mluvit o tom, že musíte volat jakoukoli knihovnu třetí strany, protože s největší pravděpodobností bude mít úzkou smyčku, která jednoduše restartuje I/O na EINTR
. Reverzní inženýrství jejich deskriptoru souboru tak, aby to zavřelo, je také nepřeruší – mohou čekat na semafor nebo jiný zdroj. V tomto případě je prostě nemožné napsat funkční kód, tečka. Ano, tohle je naprosto poškozený mozek. Promluvte si s lidmi, kteří navrhli výjimky C++ a pthread_cancel
. Pravděpodobně to může být opraveno v některé budoucí verzi C++. Hodně štěstí.
Vaše select()
může mít časový limit, i když je to málo časté, aby bylo možné za určitých podmínek ladně opustit vlákno. Já vím, průzkumy jsou na hovno...
Další alternativou je mít kanál pro každého potomka a přidat ho do seznamu deskriptorů souborů, které vlákno sleduje. Odešlete bajt do kanálu od nadřazeného prvku, když chcete, aby tento potomek skončil. Žádné dotazování za cenu potrubí na vlákno.