GNU/Linux >> Znalost Linux >  >> Linux

Jak se připojit k vláknu, které visí na blokování IO?

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.


Linux
  1. SSHFS? Co to je a jak to mohu použít?

  2. Čtyři paralelní úkoly… Jak na to?

  3. Jak ukončit vlákno v programu C ( příklad pthread_exit )

  1. Jak napsat obsluhu signálu pro zachycení SIGSEGV?

  2. Jak se vyhnout použití printf v obslužném programu signálu?

  3. Jak získat ID vlákna pthread v programu linux c?

  1. Které vlákno zpracovává signál?

  2. Jak poslat signál ke spuštění programu v kontejneru dockeru?

  3. Jak spojit dva soubory CSV?