Cosa succede a un thread separato quando esce main ()?


152

Supponiamo che io stia iniziando un std::threade quindi detach(), quindi il thread continua l'esecuzione anche se quello std::threadche una volta lo rappresentava, va fuori portata.

Supponiamo inoltre che il programma non disponga di un protocollo affidabile per unire il thread separato 1 , quindi il thread disconnesso continua a funzionare quando main()esce.

Non riesco a trovare nulla nello standard (più precisamente, nella bozza N3797 C ++ 14), che descrive cosa dovrebbe accadere, né 1.10 né 30.3 contengono una formulazione pertinente.

1 Un'altra domanda, probabilmente equivalente, è: "è possibile unire nuovamente un thread separato", poiché qualsiasi protocollo si stia inventando per unirsi, la parte di segnalazione dovrebbe essere eseguita mentre il thread era ancora in esecuzione e lo scheduler del sistema operativo potrebbe decidere di mettere in pausa il thread per un'ora subito dopo l'esecuzione della segnalazione senza che l'estremità ricevente rilevi in ​​modo affidabile che il thread sia effettivamente terminato.

Se l'esaurimento main()con l'esecuzione di thread separati è un comportamento indefinito, qualsiasi uso di std::thread::detach()è un comportamento indefinito a meno che il thread principale non esca mai 2 .

Pertanto, a corto di main()thread separati in esecuzione deve avere effetti definiti . La domanda è: dove (nello standard C ++ , non POSIX, non documenti del sistema operativo, ...) sono definiti quegli effetti.

2 Un thread separato non può essere unito (nel senso di std::thread::join()). È possibile attendere i risultati da fili indipendenti (ad esempio tramite un futuro da std::packaged_task, o da un semaforo conteggio o una bandiera e una variabile condizione), ma che non garantisce che il filo ha terminato l'esecuzione . Infatti, a meno che non si inserisca la parte di segnalazione nel distruttore del primo oggetto automatico del thread, ci saranno , in generale, dei codici (distruttori) che corrono dopo il codice di segnalazione. Se il sistema operativo pianifica il thread principale per consumare il risultato e uscire prima che il thread separato abbia terminato l'esecuzione di detti distruttori, che cosa verrà definito?


5
Posso trovare solo una nota non obbligatoria molto vaga in [basic.start.term] / 4: "Terminare ogni thread prima di una chiamata std::exito uscire da mainè sufficiente, ma non necessario, per soddisfare questi requisiti." (l'intero paragrafo può essere pertinente) Vedi anche [support.start.term] / 8 ( std::exitviene chiamato quando mainritorna)
dyp

Risposte:


45

La risposta alla domanda originale "cosa succede a un thread separato quando main()esce" è:

Continua a funzionare (perché lo standard non dice che è stato arrestato), e questo è ben definito, purché non tocchi né le variabili (automatiche | thread_local) di altri thread né oggetti statici.

Questo sembra essere autorizzato a consentire i gestori di thread come oggetti statici (la nota in [basic.start.term] / 4 dice altrettanto, grazie a @dyp per il puntatore).

I problemi sorgono quando la distruzione di oggetti statici è terminata, perché quindi l'esecuzione entra in un regime in cui può essere eseguito solo il codice consentito nei gestori di segnali ( [basic.start.term] / 1, prima frase ). Della libreria standard C ++, questa è solo la <atomic>libreria ( [support.runtime] / 9, seconda frase ). In particolare, ciò - in generale - esclude condition_variable (è definito dall'implementazione se questo deve essere salvato in un gestore di segnale, perché non fa parte di <atomic>).

A meno che tu non abbia srotolato il tuo stack a questo punto, è difficile capire come evitare comportamenti indefiniti.

La risposta alla seconda domanda "è possibile unire nuovamente i thread separati":

Sì, con la *_at_thread_exitfamiglia di funzioni ( notify_all_at_thread_exit(), std::promise::set_value_at_thread_exit(), ...).

Come notato nella nota [2] della domanda, segnalare una variabile di condizione o un semaforo o un contatore atomico non è sufficiente per unire un thread separato (nel senso di assicurare che la fine della sua esecuzione sia avvenuta prima della ricezione di detto segnale da un thread in attesa), perché, in generale, ci sarà più codice eseguito dopo, ad esempio, notify_all()di una variabile di condizione, in particolare i distruttori di oggetti automatici e thread-local.

Esecuzione la segnalazione come l'ultima cosa che il filo fa ( dopo distruttori di oggetti automatici e filo-locale è-successo ) è ciò che la _at_thread_exitfamiglia di funzioni è stata progettata per.

Quindi, al fine di evitare comportamenti indefiniti in assenza di garanzie di implementazione al di sopra di quanto richiesto dallo standard, è necessario unire (manualmente) un thread separato con una _at_thread_exitfunzione che fa la segnalazione o fare eseguire al thread separato solo codice che sarebbe sicuro per anche un gestore di segnali.


17
Sei sicuro di questo? Ovunque ho testato (GCC 5, clang 3.5, MSVC 14), tutti i thread separati vengono eliminati all'uscita del thread principale.
Rustyx,

3
Credo che il problema non sia quello che fa un'implementazione specifica, ma sia come evitare ciò che lo standard definisce un comportamento indefinito.
Jon Spencer,

7
Questa risposta sembra implicare che dopo la distruzione delle variabili statiche il processo passerà in una sorta di stato di sospensione in attesa del completamento di tutti i thread rimanenti. Ciò non è vero, dopo aver exitfinito di distruggere oggetti statici, eseguire atexitgestori, svuotare i flussi, ecc., Restituisce il controllo all'ambiente host, ovvero il processo termina. Se un thread separato è ancora in esecuzione (e in qualche modo ha evitato comportamenti indefiniti non toccando nulla al di fuori del proprio thread), scompare semplicemente in un soffio di fumo quando il processo termina.
Jonathan Wakely,

3
Se stai bene usando API C ++ non ISO, se le mainchiamate pthread_exitinvece di restituire o chiamare, exitciò farà sì che il processo attenda il completamento dei thread separati, quindi chiamerà al exittermine dell'ultimo.
Jonathan Wakely,

3
"Continua a funzionare (perché lo standard non dice che è stato arrestato)" -> Qualcuno può dirmi COME un thread può continuare l'esecuzione durante il suo processo contenitore?
Gupta,

42

Staccare le discussioni

Secondo std::thread::detach :

Separa il thread di esecuzione dall'oggetto thread, consentendo l'esecuzione in modo indipendente. Eventuali risorse allocate verranno liberate una volta terminato il thread.

Da pthread_detach:

La funzione pthread_detach () deve indicare all'implementazione che la memoria per il thread può essere recuperata quando termina quel thread. Se il thread non è terminato, pthread_detach () non deve causarne la chiusura. L'effetto di più chiamate pthread_detach () sullo stesso thread di destinazione non è specificato.

Il distacco dei thread serve principalmente per il risparmio di risorse, nel caso in cui l'applicazione non debba attendere il completamento di un thread (ad es. Demoni, che devono essere eseguiti fino al termine del processo):

  1. Per liberare l'handle dell'applicazione: Uno può lasciare un std::threadoggetto fuori dall'ambito senza unirsi, ciò che normalmente porta a una chiamatastd::terminate() distruzione.
  2. Per consentire al sistema operativo di ripulire automaticamente le risorse specifiche del thread ( TCB ) non appena il thread esce, perché abbiamo specificato esplicitamente, che non ci interessa unire il thread in un secondo momento, quindi, non è possibile unire un thread già disconnesso.

Discussioni per uccidere

Il comportamento al termine del processo è lo stesso del thread principale, che potrebbe almeno catturare alcuni segnali. Il fatto che altri thread siano in grado di gestire o meno i segnali non è così importante, poiché si potrebbero unire o terminare altri thread all'interno dell'invocazione del gestore del segnale del thread principale. (Domanda correlata )

Come già detto, qualsiasi thread, staccato o meno, morirà con il suo processo sulla maggior parte dei sistemi operativi . Il processo stesso può essere terminato sollevando un segnale, chiamando exit()o ritornando dalla funzione principale. Tuttavia, C ++ 11 non può e non cerca di definire il comportamento esatto del sistema operativo sottostante, mentre gli sviluppatori di una VM Java possono sicuramente astrarre tali differenze in una certa misura. AFAIK, i processi esotici e i modelli di threading si trovano di solito su piattaforme antiche (su cui probabilmente C ++ 11 non verrà trasferito) e vari sistemi embedded, che potrebbero avere un'implementazione speciale e / o limitata della libreria di lingue e anche un supporto linguistico limitato.

Supporto discussione

Se i thread non sono supportati, std::thread::get_id()dovrebbe essere restituito un ID non valido (costruito per impostazione predefinita std::thread::id) in quanto esiste un processo semplice, che non richiede l'esecuzione di un oggetto thread e il costruttore di a std::threaddovrebbe lanciare a std::system_error. Ecco come intendo C ++ 11 in combinazione con i sistemi operativi di oggi. Se esiste un sistema operativo con supporto per il threading, che non genera un thread principale nei suoi processi, fammi sapere.

Discussioni di controllo

Se è necessario mantenere il controllo su un thread per l'arresto corretto, è possibile farlo utilizzando i primitivi di sincronizzazione e / o una sorta di flag. Tuttavia, in questo caso, impostare un flag di spegnimento seguito da un join è il modo che preferisco, poiché non ha senso aumentare la complessità staccando i thread, poiché le risorse verrebbero comunque liberate allo stesso tempo, dove i pochi byte std::threaddell'oggetto vs. una maggiore complessità e forse più primitive di sincronizzazione dovrebbero essere accettabili.


3
Poiché ogni thread ha il suo stack (che è nell'intervallo di megabyte su Linux), sceglierei di staccare il thread (quindi il suo stack verrà liberato non appena esce) e utilizzare alcune primitive di sincronizzazione se il thread principale deve uscire (e per un corretto arresto deve unire i thread ancora in esecuzione invece di terminarli al ritorno / uscita).
Norbert Bérci,

8
Davvero non vedo come questo risponda alla domanda
MikeMB,

18

Considera il seguente codice:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>

void thread_fn() {
  std::this_thread::sleep_for (std::chrono::seconds(1)); 
  std::cout << "Inside thread function\n";   
}

int main()
{
    std::thread t1(thread_fn);
    t1.detach();

    return 0; 
}

Eseguendolo su un sistema Linux, il messaggio da thread_fn non viene mai stampato. Il sistema operativo infatti pulisce thread_fn()non appena main()esce. La sostituzione t1.detach()con t1.join()stampa sempre il messaggio come previsto.


Questo comportamento si verifica esattamente su Windows. Quindi, sembra che Windows uccida i thread staccati al termine del programma.
Gupta,

17

Il destino del thread dopo la chiusura del programma è un comportamento indefinito. Ma un moderno sistema operativo ripulirà tutti i thread creati dal processo alla chiusura.

Quando si stacca un std::thread, queste tre condizioni continueranno a essere valide:

  1. *this non possiede più alcun thread
  2. joinable() sarà sempre uguale a false
  3. get_id() sarà uguale std::thread::id()

1
Perché indefinito? Perché lo standard non definisce nulla? Secondo la mia nota a piè di pagina, questo non farebbe appello a detach()comportamenti indefiniti? Difficile da credere ...
Marc Mutz - mmutz

2
@ MarcMutz-mmutz Non è definito nel senso che se il processo termina, il destino del thread non è definito.
Cesare,

2
@Caesar e come posso assicurarmi di non uscire prima che il thread sia terminato?
MichalH,


0

Per consentire ad altri thread di continuare l'esecuzione, il thread principale dovrebbe terminare chiamando pthread_exit () anziché Exit (3). Va bene usare pthread_exit in main. Quando viene usato pthread_exit, il thread principale smetterà di essere eseguito e rimarrà nello stato zombie (defunct) fino a quando tutti gli altri thread non escono. Se si utilizza pthread_exit nel thread principale, non è possibile ottenere lo stato di restituzione di altri thread e non è possibile eseguire la ripulitura per altri thread (è possibile utilizzare pthread_join (3)). Inoltre, è meglio scollegare i thread (pthread_detach (3)) in modo che le risorse del thread vengano automaticamente rilasciate al termine del thread. Le risorse condivise non verranno rilasciate fino all'uscita di tutti i thread.


@kgvinod, perché non aggiungere "pthread_exit (0);" dopo "ti.detach ()";
yshi,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.