Question

Quelles sont les tensions entre le multithreading et la sécurité des exceptions en C ++? Existe-t-il de bonnes directives à suivre? Un fil se termine-t-il à cause d'une exception non interceptée?

Était-ce utile?

La solution

Je pense que le standard C ++ ne fait aucune mention du multithreading - le multithreading est une fonctionnalité spécifique à la plate-forme.

Je ne suis pas tout à fait sûr de ce que dit le standard C ++ à propos des exceptions non capturées en général, mais selon cette page , ce qui se passe est défini par la plate-forme, et vous devriez le savoir dans la documentation de votre compilateur.

Dans un test rapide que j'ai fait avec g ++ 4.0.1 (i686-apple-darwin8-g ++ - 4.0.1 pour être spécifique), le résultat est que terminate () est appelé, qui tue tout le programme. Le code que j'ai utilisé suit:

#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;
}

Compilé avec g ++ threadtest.cc -lpthread -o threadtest . La sortie était:

terminate called after throwing an instance of 'int'

Autres conseils

C ++ 0x aura Langage Prise en charge du transport des exceptions entre les threads afin que, lorsqu'un thread de travail lève une exception, le thread qui le crée puisse l'attraper ou le relancer.

De la proposition:

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 );
}

Une exception non interceptée appellera terminate () , qui à son tour appelle le terminate_handler (qui peut être défini par le programme). Par défaut, terminate_handler appelle abort () .

Même si vous remplacez le terminate_handler par défaut, la norme indique que la routine que vous fournissez "doit mettre fin à l'exécution du programme sans retourner à l'appelant". (ISO 14882-2003 18.6.1.3).

Donc, en résumé, une exception non capturée mettra fin au programme et pas seulement au thread.

En ce qui concerne la sécurité des threads, selon Adam Rosenfield , une chose spécifique à la plate-forme qui n'est pas traitée par la norme.

C’est la principale raison de l’existence d’Erlang.

Je ne sais pas quelle est la convention, mais à mon humble avis, soyez aussi proche de Erlang que possible. Rendez les objets de tas immuables et configurez un protocole de transmission de messages pour la communication entre les threads. Évitez les serrures. Assurez-vous que le message qui passe est protégé contre les exceptions. Conservez autant de choses avec état sur la pile.

Comme d'autres l'ont déjà mentionné, la concurrence (et la sécurité des threads en particulier) est un problème architectural qui affecte la conception de votre système et de votre application.

Mais je voudrais répondre à votre question sur la tension entre la sécurité des exceptions et la sécurité des threads.

Au niveau de la classe, thread-safety nécessite des modifications de l'interface. Tout comme le fait la sécurité d'exception. Par exemple, il est habituel que les classes renvoient des références à des variables internes, par exemple:

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

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

Si Foo est partagé par plusieurs threads, des problèmes vous attendent. Naturellement, vous pouvez mettre un mutex ou un autre verrou pour accéder à Foo. Mais assez tôt, tous les programmeurs C ++ voudraient envelopper Foo dans un "ThreadSafeFoo". Mon argument est que l'interface de Foo devrait être remplacée par:

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

  std::string value() const;
};

Oui, cela coûte plus cher, mais vous pouvez le sécuriser pour les threads avec des serrures à l'intérieur de Foo. IMnsHO, cela crée une certaine tension entre la sécurité du filetage et la sécurité des exceptions. Ou du moins, vous devez effectuer plus d’analyses, car chaque classe utilisée comme ressource partagée doit être examinée sous les deux angles.

Un exemple classique (je ne me rappelle pas où je l'ai vu en premier) est dans la bibliothèque std.

Voici comment extraire quelque chose d'une file d'attente:

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

Cette interface est quelque peu obtuse par rapport à:

T t = q.pop();

Mais cela est fait car l’affectation de copie T peut lancer. Si la copie est lancée après que la pop se soit produite, cet élément est perdu de la file d'attente et ne peut jamais être récupéré. Mais comme la copie a lieu avant que l’élément ne soit sauté, vous pouvez placer une manipulation arbitraire autour de la copie de front () dans des blocs try / catch.

L'inconvénient est que vous ne pouvez pas implémenter une file avec des threads sûrs avec l'interface std :: queue en raison des deux étapes impliquées. Ce qui est bon pour la sécurité des exceptions (séparer les étapes pouvant être émises) est maintenant mauvais pour le multithreading.

Votre principal sauveur en matière de sécurité des exceptions est que les opérations de pointeur sont sans effet. De même, les opérations de pointeur peuvent être rendues atomiques sur la plupart des plates-formes. Elles peuvent donc souvent être votre sauveur dans le code multithread. Vous pouvez avoir votre gâteau et le manger aussi, mais c'est vraiment difficile.

J'ai remarqué deux problèmes:

  • dans g ++ sous Linux, l’assassinat d’un thread (pthread_cancel) est accompli en lançant un "inconnu". exception. D'une part, cela vous permet de nettoyer proprement lorsque le fil est tué. Par contre, si vous attrapez cette exception et ne la renvoyez pas, votre code se termine par abort (). Par conséquent, si vous utilisez une des bibliothèques ou que vous utilisez l'une de ses bibliothèques, vous ne pouvez pas disposer

    attraper (...)

sans

throw;

dans votre code fileté. Ici , il est fait référence à ce comportement sur le Web:

  • Parfois, vous devez transférer une exception entre les threads. Ce n’est pas une chose facile à faire - nous avons fini par faire quelque chose, quand la solution appropriée est le type de marshalling / demarshalling que vous utiliseriez entre les processus.

Je ne recommande pas de laisser n'importe quelle exception non capturée. Enveloppez vos fonctions de fil de premier niveau dans des gestionnaires attrayants qui peuvent arrêter le programme de façon plus gracieuse (ou du moins verbalement).

Je pense que la chose la plus importante est de se rappeler que les exceptions non capturées d'autres threads ne sont pas visibles pour l'utilisateur ou ne sont pas projetées vers le thread principal. Vous devez donc contourner tout le code qui doit s’exécuter sur des threads différents du thread principal avec des blocs try / catch.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top