Domanda

Quali sono le tensioni tra il multithreading e la sicurezza delle eccezioni in C ++? Ci sono buone linee guida da seguire? Una discussione termina a causa di un'eccezione non rilevata?

È stato utile?

Soluzione

Credo che lo standard C ++ non faccia menzione del multithreading: il multithreading è una funzionalità specifica della piattaforma.

Non sono esattamente sicuro di cosa dice lo standard C ++ sulle eccezioni non rilevate in generale, ma secondo questa pagina , ciò che accade è definito dalla piattaforma e dovresti scoprirlo nella documentazione del tuo compilatore.

In un test rapido e sporco che ho fatto con g ++ 4.0.1 (i686-apple-darwin8-g ++ - 4.0.1 per essere specifici), il risultato è che terminate () è chiamato, che uccide l'intero programma. Il codice che ho usato è il seguente:

#include <stdio.h>
#include <pthread.h>

void *threadproc(void *x)
{
  throw 0;

  return NULL;
}

int main(int argc, char **argv)
{
  pthread_t t;
  pthread_create(&t, NULL, threadproc, NULL);

  void *ret;
  pthread_join(t, &ret);

  printf("ret = 0x%08x\n", ret);

  return 0;
}

Compilato con g ++ threadtest.cc -lpthread -o threadtest . L'output è stato:

terminate called after throwing an instance of 'int'

Altri suggerimenti

C ++ 0x avrà Lingua Supporto per il trasporto di eccezioni tra thread in modo che quando un thread di lavoro genera un'eccezione, il thread di generazione può catturarlo o riproporlo.

Dalla proposta:

namespace std {

    typedef unspecified exception_ptr;

    exception_ptr current_exception();
    void rethrow_exception( exception_ptr p );

    template< class E > exception_ptr copy_exception( E e );
}

Un'eccezione non rilevata chiamerà terminate () che a sua volta chiama terminate_handler (che può essere impostato dal programma). Per impostazione predefinita, terminate_handler chiama abort () .

Anche se si sovrascrive il terminate_handler predefinito, lo standard dice che la routine che fornisci " deve terminare l'esecuzione del programma senza tornare al chiamante " (ISO 14882-2003 18.6.1.3).

Quindi, in sintesi, un'eccezione non rilevata terminerà il programma non solo il thread.

Per quanto riguarda la sicurezza dei thread, come Adam Rosenfield dice, questo è una cosa specifica della piattaforma che non viene affrontata dallo standard.

Questa è la principale ragione per cui esiste Erlang.

Non so quale sia la convenzione, ma imho, sii il più possibile simile a Erlang. Rendi immutabili gli oggetti heap e imposta una sorta di protocollo di passaggio messaggi per comunicare tra i thread. Evitare i blocchi. Assicurarsi che il passaggio dei messaggi sia sicuro dalle eccezioni. Mantieni tutte le cose con stato in pila.

Come altri hanno già discusso, la concorrenza (e in particolare la sicurezza dei thread) è un problema architettonico che influisce sul modo in cui si progetta il sistema e l'applicazione.

Ma vorrei porre la tua domanda sulla tensione tra sicurezza dell'eccezione e sicurezza del thread.

A livello di classe, la sicurezza dei thread richiede modifiche all'interfaccia. Proprio come fa la sicurezza delle eccezioni. Ad esempio, è consuetudine che le classi restituiscano riferimenti a variabili interne, ad esempio:

class Foo {
public:
  void set_value(std::string const & s);

  std::string const & value() const;
};

Se Foo è condiviso da più thread, i problemi ti attendono. Naturalmente, potresti mettere un mutex o un altro lucchetto per accedere a Foo. Ma abbastanza presto, tutti i programmatori C ++ vorrebbero avvolgere Foo in un "ThreadSafeFoo". La mia tesi è che l'interfaccia per Foo dovrebbe essere cambiata in:

class Foo {
public:
  void set_value(std::string const & s);

  std::string value() const;
};

Sì, è più costoso, ma può essere reso thread-safe con lucchetti all'interno di Foo. In questo modo si crea una certa tensione tra sicurezza del filo e sicurezza dell'eccezione. O almeno, è necessario eseguire ulteriori analisi poiché ogni classe utilizzata come risorsa condivisa deve essere esaminata sotto entrambe le luci.

Un esempio classico (non ricordo dove l'ho visto prima) è nella libreria std.

Ecco come fai scoppiare qualcosa da una coda:

T t;
t = q.front(); // may throw
q.pop();

Questa interfaccia è in qualche modo ottusa rispetto a:

T t = q.pop();

Ma è fatto perché l'assegnazione della copia T può essere lanciata. Se la copia viene generata dopo che si verifica il pop, quell'elemento viene perso dalla coda e non può mai essere recuperato. Ma poiché la copia avviene prima che l'elemento venga espulso, puoi mettere una gestione arbitraria intorno alla copia da front () nei blocchi try / catch.

Lo svantaggio è che non è possibile implementare una coda protetta da thread con l'interfaccia std :: queue a causa dei due passaggi coinvolti. Ciò che è buono per la sicurezza delle eccezioni (separando i passaggi che possono essere lanciati), ora non lo è per il multithreading.

Il tuo principale salvatore, ad eccezione della sicurezza, è che le operazioni con i puntatori non sono valide. Allo stesso modo, le operazioni con i puntatori possono essere rese atomiche sulla maggior parte delle piattaforme, quindi spesso possono essere il tuo salvatore nel codice multithread. Puoi avere la tua torta e mangiarla anche tu, ma è davvero difficile.

Ci sono due problemi che ho notato:

  • in g ++ su Linux, l'uccisione di un thread (pthread_cancel) si ottiene lanciando un "sconosciuto" eccezione. Da un lato, ciò ti consente di ripulire bene quando il thread viene ucciso. D'altra parte, se catturi quell'eccezione e non la ripeti, il tuo codice termina con abort (). Pertanto, se tu o una qualsiasi delle librerie che usi kill thread, non puoi avere

    cattura (...)

senza

throw;

nel tuo codice thread. Qui è un riferimento a questo comportamento sul web:

  • A volte è necessario trasferire un'eccezione tra i thread. Questa non è una cosa facile da fare: abbiamo finito per fare qualcosa, quando la soluzione corretta è il tipo di marshalling / demarshalling che useresti tra i processi.

Non consiglio di lasciare nessuna eccezione non rilevata. Avvolgi le tue funzioni di thread di livello superiore in gestori catch-all che possono arrestare il programma in modo più grazioso (o almeno verboso).

Penso che la cosa più importante sia ricordare che le eccezioni non rilevate dagli altri thread non vengono mostrate all'utente o lanciate nel thread principale. Quindi devi deformare tutto il codice che dovrebbe essere eseguito su thread diversi rispetto al thread principale con i blocchi try / catch.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top