Comment rejoindre un thread qui bloque les e / s?

J'ai un thread en arrière-plan qui lit les événements d'un périphérique d'entrée de manière bloquante, maintenant quand je quitte l'application, je veux nettoyer le thread correctement, mais je ne peux pas simplement exécuter pthread_join() parce que le thread ne quitterait jamais en raison de l'E / S bloquante.

Comment puis-je résoudre correctement cette situation? Dois-je envoyer un pthread_kill (theard, SIGIO) ou un pthread_kill (theard, SIGALRM) pour casser le bloc? Est-ce que l'un ou l'autre de ces signaux est le bon? Ou est-il une autre façon de résoudre cette situation et de laisser ce thread enfant quitter la lecture de blocage?

Actuellement un peu perplexe car aucun de mes googling n'a trouvé de solution.

C'est sous Linux et en utilisant pthreads.

Edit: j'ai joué un peu avec SIGIO et SIGALRM, quand je n'installe pas de gestionnaire de signal, ils cassent les e / s bloquantes, mais donnent un message sur la console ("e/s possible") mais quand j'installe un gestionnaire de signal, pour éviter ce message, ils ne cassent plus les e / s bloquantes, donc le le thread ne se termine pas. Donc, je suis un peu de retour à la première étape.

24
demandé sur Grumbel 2008-10-15 08:53:28

13 réponses

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

Depuis le noyau Linux 2.6.22, le système offre une nouvelle fonction appelée {[1] } qui peut être utilisée 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 signal
f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);

Maintenant, vous pouvez utiliser les fonctions poll() ou select() pour écouter le signal le long du fichier plus habituel descripteur (socket, fichier sur disque, etc.) vous avez été à l'écoute sur.

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 application qui fonctionne avec (1) minuteurs, (2) les sockets, (3) les tuyaux, (4) signaux Unix, (5) des fichiers normaux. En fait, vraiment n'importe quel descripteur de fichier plus minuterie.

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

3
répondu Alexis Wilke 2017-07-07 00:36:21

Je recommande aussi d'utiliser un select ou un autre moyen non basé sur le signal pour terminer votre thread. Une des raisons pour lesquelles nous avons des discussions est d'essayer de sortir de la folie du signal. Qui a 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.

En ce qui concerne le comportement lorsque vous avez envoyé le signal, je suppose que il a à voir avec la façon dont vous avez géré le signal. Si aucun gestionnaire n'est 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 séparera généralement d'une lecture avec EINTR, à moins qu'il ne soit vraiment dans cet état ininterrompu comme mentionné dans une réponse précédente. Mais je pense que ce n'est pas le cas, ou vos expériences avec SIGALRM et SIGIO n'auraient pas mis fin au processus.

Est votre lecture peut-être dans une sorte de boucle? Si la lecture se termine par -1 return, 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);

}
14
répondu bog 2008-10-15 08:29:21

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

Malheureusement, cela ne peut pas être utilisé dans le code C++, jamais. Tout code C++ std lib, ou tout try {} catch() sur la pile appelante au moment de {[0] } sera potentiellement segvi tuant votre processus entier.

La seule solution consiste à gérer SIGUSR1, en définissant un indicateur d'arrêt, pthread_kill(SIGUSR1), puis 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 Les 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 de parler si vous devez appeler une lib tierce partie, car ils auront 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 le coupera pas non plus-ils pourraient attendre sur un sémaphore ou une autre ressource. Dans ce cas, il est tout simplement impossible d'écrire du code de travail, période. Oui, c'est tout à fait cerveau endommagé. Parlez aux gars qui ont conçu des exceptions C++ et pthread_cancel. Soi-disant cela peut être corrigé dans une future version de C++. Bonne chance avec ça.

14
répondu qqq 2013-11-18 02:57:39

Votre select() pourrait avoir un délai d'attente, même s'il est peu fréquent, afin de quitter le thread gracieusement à une certaine condition. Je sais, les bureaux de vote suce...

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

9
répondu Craig McQueen 2013-11-18 02:59:24

Dépend comment il attend IO.

Si le thread est dans l'état "Uninterruptible IO" (montré comme " D " en haut), alors il n'y a absolument rien que vous puissiez faire à ce sujet. Les Threads n'entrent normalement que brièvement dans cet état, en faisant quelque chose comme attendre qu'une page soit permutée (ou chargée à la demande, par exemple à partir d'un fichier mmap ou d'une bibliothèque partagée, etc.), mais un échec (en particulier d'un serveur NFS) peut le faire rester dans cet état plus longtemps.

Il n'y a vraiment aucun moyen de s'échapper de cet état "D". Le thread ne répondra pas aux signaux (Vous pouvez les envoyer, mais ils seront mis en file d'attente).

Si c'est une fonction d'E / S normale telle que read(), write() ou une fonction d'attente telle que select() ou poll(), les signaux seraient livrés normalement.

6
répondu MarkR 2013-12-04 23:37:16

Une solution qui m'est venue à l'esprit la dernière fois que j'ai eu un problème comme celui - ci était de créer un fichier (par exemple. un tuyau) qui n'existait que dans le but de réveiller les threads bloquants.

L'idée serait de créer un fichier à partir de la boucle principale (ou 1 par thread, comme le suggère timeout - cela vous donnerait un contrôle plus fin sur les threads qui sont réveillés). Tous les threads qui bloquent les e / s de fichiers feraient un select (), en utilisant le ou les fichiers sur lesquels ils essaient d'opérer, ainsi que le fichier créé par la boucle principale (en tant que membre du jeu de descripteurs de fichier de lecture). Cela devrait faire revenir tous les appels select ().

Code pour gérer cet "événement" de la boucle principale devra être ajouté à chacun des threads.

Si la boucle principale devait réveiller tous les threads, elle pourrait soit écrire dans le fichier, soit le fermer.


Je ne peux pas dire avec certitude si cela fonctionne, car une restructuration signifiait que le besoin de l'essayer a disparu.

3
répondu Andrew Edgecombe 2008-10-15 07:16:52

Je pense que, comme vous l'avez dit, le seul moyen serait d'envoyer un signal puis de le capturer et de le gérer de manière appropriée. Les Alternatives peuvent être SIGTERM, SIGUSR1, SIGQUIT,SIGHUP, SIGINT, etc.

Vous pouvez également utiliser select () sur votre descripteur d'entrée afin de ne lire que lorsqu'il est prêt. Vous pouvez utiliser select () avec un délai d'attente d'une seconde, par exemple, puis vérifier si ce thread doit se terminer.

2
répondu Chris 2008-10-15 05:47:47

J'ajoute toujours une fonction " kill " liée à la fonction de thread que j'exécute avant jointure qui garantit que le thread sera joignable dans un délai raisonnable. Quand un thread utilise des e / s bloquantes, j'essaie d'utiliser le système pour casser le verrou. Par exemple, lors de l'utilisation d'un socket, j'aurais kill call shutdown(2) ou close(2) ce qui entraînerait la fin nette de la pile réseau.

L'implémentation du socket de Linux est sécurisée par thread.

1
répondu David Holm 2008-10-15 07:43:01

Je suis surpris que personne n'ait suggéré pthread_cancel. J'ai récemment écrit un programme d'E/S multi-thread et appelé cancel () et la jointure() a ensuite fonctionné très bien.

J'avais initialement essayé la pthread_kill () mais j'ai fini par terminer tout le programme avec les signaux avec lesquels j'ai testé.

1
répondu HUAGHAGUAH 2008-10-25 23:26:06

Si vous bloquez dans une bibliothèque tierce qui boucle sur EINTR, vous pouvez envisager une combinaison d'utilisation de pthread_kill avec un signal (USR1 etc) appelant une fonction vide (pas SIG_IGN) avec la fermeture/le remplacement du descripteur de fichier en question. En utilisant dup2 pour remplacer le fd par /dev / null ou similaire, vous obtiendrez un résultat de fin de fichier à la bibliothèque tierce lorsqu'elle réessayera la lecture.

Notez qu'en dup () ing le socket d'origine en premier, vous pouvez éviter d'avoir besoin pour fermer réellement le socket.

1
répondu bdonlan 2009-06-16 20:28:33

Signaux et thread est un problème subtil sur Linux selon les différentes pages de manuel. Utilisez-vous LinuxThreads, ou NPTL (si vous êtes sous Linux)?

Je ne suis pas sûr de cela, mais je pense que le gestionnaire de signal affecte l'ensemble du processus, donc soit vous terminez tout votre processus, soit tout continue.

Vous devez utiliser select ou poll chronométré, et définir un indicateur global pour terminer votre thread.

0
répondu shodanex 2008-10-15 07:57:20

Je pense que l'approche la plus propre aurait le thread en utilisant des variables conditionnelles dans une boucle pour continuer.

Lorsqu'un événement d'e/s est déclenché, le conditionnel doit être signalé.

Le thread principal pourrait simplement signaler la condition tout en chantant le prédicat de boucle à false.

Quelque Chose comme:

while (!_finished)
{
    pthread_cond_wait(&cond);
    handleio();
}
cleanup();

Rappelez-vous avec des variables conditionnelles pour gérer correctement les signaux. Ils peuvent avoir des choses telles que des "éveils fallacieux". Donc, je voudrais envelopper votre propre fonction autour de la fonction cond_wait.

0
répondu Nicholas Mancuso 2008-10-15 13:58:03
struct pollfd pfd;
pfd.fd = socket;
pfd.events = POLLIN | POLLHUP | POLLERR;
pthread_lock(&lock);
while(thread_alive)
{
    int ret = poll(&pfd, 1, 100);
    if(ret == 1)
    {
        //handle IO
    }
    else
    {
         pthread_cond_timedwait(&lock, &cond, 100);
     }
}
pthread_unlock(&lock);

Thread_alive est une variable spécifique au thread qui peut être utilisée en combinaison avec le signal pour tuer le thread.

En ce qui concerne la section handle IO, vous devez vous assurer que vous avez utilisé open avec L'option O_NOBLOCK, ou si c'est un socket il y a un drapeau similaire, vous pouvez définir MSG_NOWAIT??. pour d'autres fds Je ne suis pas sûr

0
répondu luke 2008-10-15 16:07:22