GNU/Linux >> Tutoriels Linux >  >> Linux

Comment rejoindre un fil qui bloque le blocage des E/S ?

Je recommanderais également d'utiliser une sélection ou un autre moyen non basé sur le signal pour terminer votre fil. L'une des raisons pour lesquelles nous avons des discussions est d'essayer de nous éloigner de la folie des signaux. Cela dit...

Généralement on utilise pthread_kill() avec SIGUSR1 ou SIGUSR2 pour envoyer un signal au thread. Les autres signaux suggérés - SIGTERM, SIGINT, SIGKILL - ont une sémantique à l'échelle du processus qui ne vous intéresse peut-être pas.

Quant au comportement lorsque vous avez envoyé le signal, je suppose que cela a à voir avec la façon dont vous avez géré le signal. Si vous n'avez pas de gestionnaire installé, l'action par défaut de ce signal est appliquée, mais dans le contexte du thread qui a reçu le signal. Ainsi, SIGALRM, par exemple, serait "géré" par votre thread, mais la gestion consisterait à terminer le processus - probablement pas le comportement souhaité.

La réception d'un signal par le thread le fera généralement sortir d'une lecture avec EINTR, à moins qu'il ne soit vraiment dans cet état ininterruptible comme mentionné dans une réponse précédente. Mais je pense que non, sinon vos expériences avec SIGALRM et SIGIO n'auraient pas mis fin au processus.

Votre lecture est-elle peut-être dans une sorte de boucle ? Si la lecture se termine par -1 return, alors sortez de cette boucle et quittez le thread.

Vous pouvez jouer avec ce code très bâclé que j'ai mis en place pour tester mes hypothèses - je suis à quelques fuseaux horaires de mes livres POSIX en ce moment...

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

}

Vieille question qui pourrait très bien recevoir une nouvelle réponse car les choses ont évolué et une nouvelle technologie est maintenant disponible pour mieux gérer les signaux dans les threads.

Depuis le noyau Linux 2.6.22, le système propose une nouvelle fonction appelée signalfd() qui peut être utilisé pour ouvrir un descripteur de fichier pour un ensemble donné de signaux Unix (en dehors de ceux qui tuent carrément un processus.)

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

Vous pouvez maintenant utiliser le poll() ou select() fonctions pour écouter le signal avec le descripteur de fichier plus habituel (socket, fichier sur disque, etc.) que vous écoutiez.

Le NONBLOCK est important si vous voulez une boucle qui peut vérifier les signaux et autres descripteurs de fichier encore et encore (c'est-à-dire qu'il est également important sur votre autre descripteur de fichier).

J'ai une telle implémentation qui fonctionne avec (1) des minuteries, (2) des sockets, (3) des pipes, (4) des signaux Unix, (5) des fichiers normaux. En fait, vraiment n'importe quel descripteur de fichier plus les minuteries.

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

Vous pouvez également être intéressé par des bibliothèques telles que libevent


La manière canonique de le faire est avec pthread_cancel , où le thread a fait pthread_cleanup_push /pop pour fournir un nettoyage pour toutes les ressources qu'il utilise.

Malheureusement, cela ne peut PAS être utilisé dans le code C++, jamais. N'importe quel code de bibliothèque standard C++ ou N'IMPORTE QUEL try {} catch() sur la pile appelante au moment de pthread_cancel segvi tuera potentiellement tout votre processus.

La seule solution consiste à gérer SIGUSR1 , définition d'un drapeau d'arrêt, pthread_kill(SIGUSR1) , alors partout où le thread est bloqué sur les E/S, si vous obtenez EINTR vérifiez l'indicateur d'arrêt avant de réessayer l'E/S. En pratique, cela ne réussit pas toujours sous Linux, je ne sais pas pourquoi.

Mais dans tous les cas, il est inutile d'en parler si vous devez appeler une bibliothèque tierce, car ils auront très probablement une boucle serrée qui redémarre simplement les E/S sur EINTR . L'ingénierie inverse de leur descripteur de fichier pour le fermer ne suffira pas non plus - ils pourraient attendre un sémaphore ou une autre ressource. Dans ce cas, il est tout simplement impossible d'écrire du code fonctionnel, point final. Oui, c'est complètement endommagé au cerveau. Parlez aux gars qui ont conçu les exceptions C++ et pthread_cancel . Soi-disant, cela pourrait être corrigé dans une future version de C++. Bonne chance avec ça.


Votre select() pourrait avoir un délai d'attente, même s'il est peu fréquent, afin de quitter le thread avec élégance sous certaines conditions. Je sais, les sondages, c'est nul...

Une autre alternative consiste à avoir un tube pour chaque enfant et à l'ajouter à la liste des descripteurs de fichiers surveillés par le thread. Envoyez un octet au tube depuis le parent lorsque vous souhaitez que cet enfant sorte. Pas d'interrogation au prix d'un tuyau par thread.


Linux
  1. Comment terminer un thread dans un programme C ( exemple pthread_exit )

  2. Comment nommer un thread sous Linux ?

  3. Comment écrire un gestionnaire de signal pour attraper SIGSEGV ?

  4. Comment obtenir l'identifiant de thread d'un pthread dans le programme linux c?

  5. Quel thread gère le signal ?

Comment joindre deux fichiers texte sous Linux

Comment signaler la fin de l'entrée Stdin ?

Comment installer l'application Signal Messaging sur Ubuntu 20.04

Comment joindre deux fichiers CSV ?

Comment joindre/fusionner plusieurs fichiers mp3 ?

Comment trouver la source d'un signal POSIX