2016-06-07 69 views
8

Si consideri il seguente programma:C++: Termina chiamata, senza eccezione attiva (GCC)

#include <iostream> 
#include <pthread.h> 
#include <stdexcept> 
#include <unistd.h> 

static void* busy(void*) 
{ 
    int oldstate ; 
    auto result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldstate) ; 
    if (result != 0) 
#ifdef NOEXCEPT 
    { std::cerr << "pthread_setcanceltype" << std::endl ; abort() ; } 
#else 
    throw std::runtime_error("pthread_setcanceltype") ; 
#endif 
    while (true) 
    ; 
    return nullptr ; 
} 

static pthread_t start() 
{ 
    pthread_t t ; 
    int result = pthread_create(&t,nullptr,busy,nullptr) ; 
    if (result != 0) 
    throw std::runtime_error("pthread_create") ; 
    return t ; 
} 

static void terminate(pthread_t t) 
{ 
    auto result = pthread_cancel(t) ; 
    if (result != 0) 
    throw std::runtime_error("pthread_cancel()") ; 
    result = pthread_join(t,nullptr) ; 
    if (result != 0) 
    throw std::runtime_error("pthread_join()") ; 
} 

int main() 
{ 
    auto t = start() ; 
    sleep(1) ; // may not cause an abort otherwise 
    terminate(t) ; 
    return 0 ; 
} 

Questo funziona bene finché non l'ottimizzazione (o -O1) viene utilizzato, ad esempio, con g ++ -std = C++ 11 -Wall -o test test.cc -pthread

Tuttavia, con -O2 o -O3 il programma si interrompe con il messaggio sopra.

Anche un po 'interessante: viene eseguito se compilato con -DNOEXCEPT. Quindi, se una discussione viene cancellata in una funzione che potenzialmente [sic!] Genera un'eccezione, e se l'ottimizzazione è attivata, il programma potrebbe interrompersi. - E non riesco a vedere alcun modo per impedirlo.

È riproducibile per me su amd64 gcc 4.8.4 (Ubuntu 14.04.3) e armv7l gcc 4.9.2 (Raspbian 4.9.2-10).

Puoi riprodurre questo? Hai una spiegazione? Questo comportamento sembra strano (almeno per me). Sarei felice di ricevere una sorta di feedback. Grazie!

+5

Credo che il posto si parla (si prega di collegamento quando ci si riferisce a qualcosa sul World Wide Web) stava parlando 'std :: thread' e non su pthreads. – molbdnilo

+1

Un altro [articolo utile] (https://skaark.wordpress.com/2010/08/26/pthread_cancel-considered-harmful/). Sebbene non sia esattamente il tuo problema, delinea altri problemi con 'pthread_cancel'. Modifica: un altro [problema simile] (https://gcc.gnu.org/ml/gcc/2007-06/msg00020.html) –

+0

Stai usando pthreads, non thread di C++ 11. Non c'è cancellazione del thread nei thread C++ 11. Il modo in cui i pthreads nudi interagiscono con le eccezioni C++ o con le funzionalità di thread C++ 11 (che probabilmente sono ancora pthreads all'interno della piattaforma, ma racchiuse in un qualche tipo di wrapper con C++), nessuno lo sa. –

risposta

2

Su Linux (come sulla maggior parte dei sistemi operativi) eccezioni sono una caratteristica del linguaggio-agnostico, e la cancellazione pthread è implementato utilizzando eccezioni indipendente dal linguaggio (si veda ad esempio Cancellation and C++ Exceptions).

Quando una cancellazione di pthread viene consegnata a un thread (utilizzando un segnale, ma non è necessario saperlo), il meccanismo di svolgimento richiama tutte le personalità installate in modo che possano eseguire una pulizia specifica della lingua prima dell'uscita dal thread . (Questo è abbastanza bello, significa che come nell'articolo sopra è possibile inserire un blocco catch per abi::__forced_unwind per rilevare, anche se non impedire, un annullamento del thread.)

Il problema è che una cancellazione asincrona può verificarsi in qualsiasi istruzione, e le tabelle di eccezione C++ generate da g ++ gestiscono solo eccezioni che si verificano su istruzioni note per essere in grado di generare eccezioni (cioè, ma non solo chiamate a funzioni di lancio di eccezioni). Se viene generata un'eccezione in un punto non coperto dalle tabelle C++, la personalità del C++ va in panico e termina il processo (quindi "termina chiamata senza un'eccezione attiva").

Il motivo per cui questo è influenzato dall'ottimizzazione è che la personalità C++ è installata pigramente, ma con livelli di ottimizzazione più elevati il ​​compilatore potrebbe decidere di installare preventivamente la personalità C++. È possibile garantire il crash anche a livelli di ottimizzazione inferiori esercitando il meccanismo di eccezione C++, ad es. con try { throw 0; } catch (int) {}.

La soluzione più semplice è garantire che la personalità C++ non sia installata nel thread che si desidera annullare in modo asincrono. Puoi assicurarlo compilando la funzione thread come C e non richiamando alcuna funzione C++ da esso.

A più hacky e altamente soluzione supportata è garantire che tutti i punti di cancellazione asincroni (cioè tutte le istruzioni in cui il filo annullata potrebbe essere quando si riceve la cancellazione asincrono) sono infatti coperti dal Tabelle di svolgimento C++. In primo luogo è necessario compilare con -fnon-call-exceptions; in secondo luogo devi assicurarti che ogni istruzione che potrebbe essere un punto di annullamento asincrono sia tra due punti noti come punti di annullamento sincrono, ad es. pthread_testcancel:

static void* busy(void*) 
{ 
    int oldstate ; 
    auto result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&oldstate) ; 
    if (result != 0) 
#ifdef NOEXCEPT 
    { std::cerr << "pthread_setcanceltype" << std::endl ; abort() ; } 
#else 
    throw std::runtime_error("pthread_setcanceltype") ; 
#endif 
    pthread_testcancel(); 
    for (unsigned i = 1; ; ++i) 
    if (i == 0) 
     pthread_testcancel(); 
    return nullptr ; 
} 
+0

Grazie mille per l'intuizione! :-) Se ho capito bene, la gestione delle eccezioni del C++ copre i pthreads. È anche consigliabile che l'eliminazione di pthread sia implementata in aggiunta alle eccezioni C++ (https://groups.google.com/forum/#!topic/comp.programming.threads/ZSvPGG-79pc). Tuttavia, per dirla in parole povere, non sempre funziona con GCC (poiché la sua implementazione della tabella delle eccezioni non copre tutti i punti di cancellazione). Quindi, il modo sicuro è quello di eliminare la cancellazione asincrona o di tornare alla semplice C. Una soluzione alternativa potrebbe essere quella di introdurre ulteriori punti di cancellazione (come nel tuo esempio). – mikro77

-1

Qualcuno ha scritto qui che un programma interrompe "quando un oggetto thread esce dal campo di applicazione ed è in stato joinable".

Questo è ciò che 39.3.1.3/1 [distruttore thread] dice in realtà:

Se unibili(), chiama std :: terminate(). [...] Quindi il programmatore deve assicurarsi che il distruttore non venga mai eseguito mentre il thread è ancora unificabile.

+0

Il codice in questione usa 'pthread', non' std :: thread'. –

+0

Irrilevante per la domanda del tutto. – SergeyA

+0

Beh, in realtà ho risposto alla prima frase. Il resto della domanda si basa su un'ipotesi errata e tratta i thread _wrong_ (diciamolo), quindi ... è taggato 'C++ 11' e i thread fanno parte della libreria di template standard dal C++ 11 come per quanto ne so. – skypjack