web-dev-qa-db-fra.com

Est-il prudent de couper d'un fil?

Laissez-moi vous expliquer: j’ai déjà développé une application sous Linux qui demande et exécute un fichier binaire externe et attend sa fin. Les résultats sont communiqués par les fichiers shm propres au processus fork +. Le code entier est encapsulé dans une classe.

J'envisage maintenant d'enfiler le processus afin d'accélérer les choses. Avoir de nombreuses instances différentes de fonctions de classe divise et exécute le binaire simultanément (avec des paramètres différents) et communique les résultats avec leurs propres fichiers shm uniques.

Ce fil est-il sûr? Si j'achète un fil, en plus d'être en sécurité, y a-t-il quelque chose que je dois surveiller? Tout conseil ou aide est très apprécié!

38
Ælex

forking, même avec des threads, est sans danger. Une fois que vous avez créé un fork, les threads sont indépendants par processus. (C'est-à-dire que le filetage est orthogonal à la fourche). Cependant, si les threads dans différents processus utilisent la même mémoire partagée pour communiquer, vous devez concevoir un mécanisme de synchronisation.

0
Diego Sevilla

Le problème est que fork () ne copie que le thread appelant, et tous les mutex contenus dans des threads enfants seront définitivement verrouillés dans l'enfant forké. La solution pthread était les gestionnaires pthread_atfork(). L'idée était que vous pouvez enregistrer 3 gestionnaires: un préfork, un gestionnaire parent et un gestionnaire enfant. Lorsque fork() survient, prefork est appelé avant de fork et doit obtenir tous les mutex d'application. Le parent et l'enfant doivent libérer tous les mutex dans les processus parent et enfant respectivement. 

Ce n'est pas la fin de l'histoire cependant! Les bibliothèques appellent pthread_atfork pour enregistrer les gestionnaires pour les mutex spécifiques aux bibliothèques, par exemple, Libc le fait. C’est une bonne chose: l’application ne peut pas connaître les mutex détenus par des bibliothèques tierces. Chaque bibliothèque doit donc appeler pthread_atfork pour s’assurer que ses propres mutex sont nettoyés en cas de fork()

Le problème est que l'ordre dans lequel les gestionnaires pthread_atfork sont appelés pour des bibliothèques non liées n'est pas défini (cela dépend de l'ordre dans lequel les bibliothèques sont chargées par le programme). Cela signifie donc que techniquement, une impasse peut survenir à l'intérieur d'un gestionnaire de pré-fourche en raison d'une situation de concurrence critique. 

Par exemple, considérons cette séquence: 

  1. Activer les appels T1 fork()
  2. gestionnaires de prefork pour la libc obtenus en T1
  3. Ensuite, dans le thread T2, une bibliothèque tierce A acquiert son propre mutex AM, puis effectue un appel à la bibliothèque qui nécessite un mutex. Cela bloque, car les mutex de libc sont tenus par T1.
  4. Le thread T1 exécute le gestionnaire prefork pour la bibliothèque A, qui bloque l'attente pour obtenir AM, qui est détenu par T2.

Il y a votre impasse et elle n'a aucun lien avec vos propres mutex ou code. 

C'est ce qui est arrivé à un projet sur lequel j'ai déjà travaillé. Le conseil que j'avais trouvé à l'époque était de choisir une fourchette ou des fils mais pas les deux. Mais pour certaines applications, ce n'est probablement pas pratique.

51
Kevin

Vous pouvez sans problème insérer dans un programme multithread aussi longtemps que vous êtes very attentif au code entre fork et exec. Vous ne pouvez effectuer que des appels système ré-entrants (c'est-à-dire asynchronous-safe) dans cette période. En théorie, vous n’êtes pas autorisé à malloc ou à free, bien que, dans la pratique, l’allocateur Linux par défaut soit sûr et que les bibliothèques Linux en dépendent maintenant. Le résultat final est que vous devez utilisez l’allocateur par défaut.

9
Igor Nazarenko

Bien que can utilisiez le support NPTL pthreads(7) de votre programme pour votre programme, les threads sont difficiles à adapter aux systèmes Unix, comme vous l’avez découvert avec votre question fork(2).

Étant donné que fork(2) est un très économique opération sur des systèmes modernes, vous feriez peut-être mieux de simplement fork(2) votre processus lorsque vous avez davantage de manipulations à effectuer. Cela dépend du volume de données que vous souhaitez déplacer, la philosophie de partage de processus forked est utile pour réduire les bugs liés aux données partagées, mais cela signifie que vous _ devez créer des canaux pour transférer les données entre les processus ou utilisez la mémoire partagée (shmget(2) ou shm_open(3)).

Mais si vous choisissez d’utiliser le threading, vous pouvez fork(2) un nouveau processus, avec les astuces suivantes de la page de manuel fork(2):

   *  The child process is created with a single thread — the
      one that called fork().  The entire virtual address space
      of the parent is replicated in the child, including the
      states of mutexes, condition variables, and other pthreads
      objects; the use of pthread_atfork(3) may be helpful for
      dealing with problems that this can cause.
6
sarnold

De retour à l'aube du temps, nous avons appelé les threads «processus légers», car même s'ils agissent comme des processus, ils ne sont pas identiques. La plus grande différence réside dans le fait que les threads, par définition, résident dans le même espace adresse d'un processus. Cela présente des avantages: le passage d’un thread à l’autre est rapide, ils partagent la mémoire de manière inhérente, ce qui permet des communications inter-threads rapides, et la création et la suppression de threads est rapide.

La distinction est faite ici avec les "processus lourds", qui sont des espaces d'adressage complets. Un nouveau processus lourd est créé par fork (2) . Lorsque la mémoire virtuelle est entrée dans le monde UNIX, elle a été complétée par vfork (2) et quelques autres.

A fork (2) copie tout l'espace d'adressage du processus, y compris tous les registres, et place ce processus sous le contrôle du planificateur de système d'exploitation; la prochaine fois que le planificateur se présentera, le compteur d'instructions sera relevé à l'instruction suivante - le processus enfant créé est un clone du parent. (Si vous voulez exécuter un autre programme, par exemple, parce que vous écrivez un shell, vous suivez la fourchette avec un appel exec (2) , qui charge ce nouvel espace adresse avec un nouveau programme, remplaçant celui qui était cloné.)

Fondamentalement, votre réponse est enterrée dans cette explication: quand vous avez un processus avec beaucoup LWP et vous lancez le processus, vous aurez deux processus indépendants avec plusieurs threads, s'exécutant simultanément.

Cette astuce est même utile: dans de nombreux programmes, vous avez un processus parent qui peut avoir plusieurs threads, dont certains jettent de nouveaux processus enfants. (Par exemple, un serveur HTTP peut faire cela: chaque connexion au port 80 est gérée par un thread, puis un processus enfant pour quelque chose comme un programme CGI pourrait être créé; exec (2) serait alors appelé exécutez le programme CGI à la place de la fermeture du processus parent.)

3
Charlie Martin

Si vous appelez rapidement exec ou _exit dans le processus fils créé, vous êtes ok en pratique.

Vous voudrez peut-être utiliser posix_spawn () à la place, ce qui fera probablement la bonne chose.

1
MarkR

Si vous utilisez l'appel système unix 'fork ()', vous n'utilisez techniquement pas de threads. Vous utilisez des processus. Ils disposeront de leur propre espace mémoire et ne pourront donc pas interférer les uns avec les autres. 

Tant que chaque processus utilise des fichiers différents, il ne devrait y avoir aucun problème. 

0
Kevin