GNU/Linux >> Linux Esercitazione >  >> Linux

Come partecipare a un thread sospeso sul blocco dell'IO?

Anch'io consiglierei di utilizzare una selezione o qualche altro mezzo non basato sul segnale per terminare il thread. Uno dei motivi per cui abbiamo i thread è cercare di allontanarci dalla follia dei segnali. Detto questo...

Generalmente si usa pthread_kill() con SIGUSR1 o SIGUSR2 per inviare un segnale al thread. Gli altri segnali suggeriti--SIGTERM, SIGINT, SIGKILL--hanno una semantica a livello di processo che potrebbe non interessarti.

Per quanto riguarda il comportamento quando hai inviato il segnale, la mia ipotesi è che abbia a che fare con il modo in cui hai gestito il segnale. Se non è installato alcun gestore, viene applicata l'azione predefinita di quel segnale, ma nel contesto del thread che ha ricevuto il segnale. Quindi SIGALRM, ad esempio, verrebbe "gestito" dal tuo thread, ma la gestione consisterebbe nel terminare il processo, probabilmente non il comportamento desiderato.

La ricezione di un segnale da parte del thread generalmente lo interromperà da una lettura con EINTR, a meno che non sia veramente in quello stato ininterrotto come menzionato in una risposta precedente. Ma penso di no, altrimenti i tuoi esperimenti con SIGALRM e SIGIO non avrebbero terminato il processo.

La tua lettura è forse in una sorta di loop? Se la lettura termina con -1 return, esci da quel ciclo ed esci dal thread.

Puoi giocare con questo codice molto sciatto che ho messo insieme per testare le mie ipotesi:al momento sono a un paio di fusi orari dai miei libri 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);

}

Vecchia domanda che potrebbe benissimo trovare una nuova risposta man mano che le cose si sono evolute e una nuova tecnologia è ora disponibile per meglio gestire i segnali nei thread.

Dal kernel Linux 2.6.22, il sistema offre una nuova funzione chiamata signalfd() che può essere utilizzato per aprire un descrittore di file per un dato insieme di segnali Unix (al di fuori di quelli che interrompono completamente un processo.)

// 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);

Ora puoi usare il poll() o select() funzioni per ascoltare il segnale lungo il più comune descrittore di file (socket, file su disco, ecc.) su cui stavi ascoltando.

Il NONBLOCK è importante se vuoi un ciclo che possa controllare segnali e altri descrittori di file più e più volte (ovvero è importante anche sull'altro tuo descrittore di file).

Ho una tale implementazione che funziona con (1) timer, (2) socket, (3) pipe, (4) segnali Unix, (5) file regolari. In realtà, qualsiasi descrittore di file più i 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

Potresti anche essere interessato a librerie come libevent


Il modo canonico per farlo è con pthread_cancel , dove il thread ha eseguito pthread_cleanup_push /pop per fornire pulizia per tutte le risorse che sta utilizzando.

Sfortunatamente questo NON può essere mai usato nel codice C++. Qualsiasi codice C++ std lib o QUALSIASI try {} catch() sullo stack delle chiamate al momento del pthread_cancel potenzialmente segvi interromperà l'intero processo.

L'unica soluzione è gestire SIGUSR1 , impostando un flag di arresto, pthread_kill(SIGUSR1) , quindi ovunque il thread sia bloccato su I/O, se ottieni EINTR controllare il flag di arresto prima di ritentare l'I/O. In pratica, questo non sempre riesce su Linux, non so perché.

Ma in ogni caso è inutile parlare se devi chiamare qualsiasi lib di terze parti, perché molto probabilmente avranno un ciclo stretto che riavvia semplicemente l'I/O su EINTR . Anche il reverse engineering del loro descrittore di file per chiuderlo non lo taglierà:potrebbero essere in attesa di un semaforo o di un'altra risorsa. In questo caso, è semplicemente impossibile scrivere codice funzionante, punto. Sì, questo è completamente danneggiato al cervello. Parla con i ragazzi che hanno progettato le eccezioni C++ e pthread_cancel . Presumibilmente questo potrebbe essere risolto in qualche versione futura di C++. Buona fortuna.


Il tuo select() potrebbe avere un timeout, anche se non frequente, per uscire dal thread con garbo a una certa condizione. Lo so, i sondaggi fanno schifo...

Un'altra alternativa è avere una pipe per ogni figlio e aggiungerla all'elenco dei descrittori di file controllati dal thread. Invia un byte alla pipe dal genitore quando vuoi che quel figlio esca. Nessun polling al costo di una pipe per thread.


Linux
  1. Quattro compiti in parallelo... Come farlo?

  2. Come scrivere un gestore di segnale per catturare SIGSEGV?

  3. Come evitare di usare printf in un gestore di segnale?

  4. Come unire due file CSV?

  5. Come unire/unire molti file mp3?

Come installare l'app di messaggistica Signal su Ubuntu 20.04

Gestione del segnale con più thread in Linux

Come ottenere l'ID thread di un pthread nel programma linux c?

Quale thread gestisce il segnale?

Come inviare un segnale al programma eseguito in un contenitore docker?

Come sapere che Java è installato in un sistema Linux?