Threads vs processus sous Linux

253

J'ai récemment entendu quelques personnes dire que sous Linux, il est presque toujours préférable d'utiliser des processus plutôt que des threads, car Linux est très efficace dans la gestion des processus et parce qu'il y a tellement de problèmes (comme le verrouillage) associés aux threads. Cependant, je suis méfiant, car il semble que les threads pourraient donner un gain de performances assez important dans certaines situations.

Donc, ma question est la suivante: face à une situation que les threads et les processus peuvent tous les deux gérer assez bien, dois-je utiliser des processus ou des threads? Par exemple, si j'écrivais un serveur Web, devrais-je utiliser des processus ou des threads (ou une combinaison)?

user17918
la source
Y a-t-il une différence avec Linux 2.4?
mouviciel
3
La différence entre les processus et les threads sous Linux 2.4 est que les threads partagent plus de parties de leur état (espace d'adressage, descripteurs de fichiers, etc.) que les processus, ce qui n'est généralement pas le cas. Le NPTL sous Linux 2.6 rend cela un peu plus clair en leur donnant des "groupes de threads" qui sont un peu comme des "processus" dans win32 et Solaris.
MarkR
6
La programmation simultanée est difficile. Sauf si vous avez besoin de performances très élevées, l'aspect le plus important de votre compromis sera souvent la difficulté de débogage . Les processus rendent la solution beaucoup plus facile à cet égard, car toute communication est explicite (facile à vérifier, à enregistrer, etc.). En revanche, la mémoire partagée des fils crée des gazillions d'endroits où un fil peut avoir un impact erroné sur un autre.
Lutz Prechelt du
1
@LutzPrechelt - La programmation simultanée peut être multi-thread ainsi que multi-processus. Je ne vois pas pourquoi vous supposez que la programmation simultanée est multithread uniquement. Cela peut être dû à certaines limitations linguistiques, mais en général, cela peut être les deux.
iankit
2
Je lie Lutz a simplement déclaré que la programmation simultanée est difficile quel que soit le choix - processus ou threads - mais que la programmation simultanée utilisant des processus facilite le débogage dans de nombreux cas.
user2692263

Réponses:

322

Linux utilise un modèle de thread 1-1, avec (pour le noyau) aucune distinction entre les processus et les threads - tout est simplement une tâche exécutable. *

Sous Linux, l'appel système cloneclone une tâche, avec un niveau de partage configurable, parmi lesquels:

  • CLONE_FILES: partage la même table de descripteurs de fichiers (au lieu de créer une copie)
  • CLONE_PARENT: ne pas établir de relation parent-enfant entre la nouvelle tâche et l'ancienne (sinon, child getppid()= parent's getpid())
  • CLONE_VM: partager le même espace mémoire (au lieu de créer une copie COW )

fork()appelle le clone(moins de partage )et pthread_create()appelle le clone(plus de partage ). **

forking coûte un tout petit peu plus pthread_createqu'ing en raison de la copie des tables et de la création de mappages COW pour la mémoire, mais les développeurs du noyau Linux ont essayé (et réussi) à minimiser ces coûts.

La commutation entre les tâches, si elles partagent le même espace mémoire et différentes tables, sera un peu moins chère que si elles ne sont pas partagées, car les données peuvent déjà être chargées dans le cache. Cependant, le changement de tâches est toujours très rapide même si rien n'est partagé - c'est autre chose que les développeurs du noyau Linux essaient d'assurer (et réussissent à assurer).

En fait, si vous êtes sur un système multiprocesseur, le non- partage peut en fait être bénéfique pour les performances: si chaque tâche s'exécute sur un processeur différent, la synchronisation de la mémoire partagée coûte cher.


* Simplifié. CLONE_THREADprovoque la distribution des signaux à partager (qui a besoin CLONE_SIGHAND, qui partage la table de gestion des signaux).

** Simplifié. Il existe à la fois SYS_forket SYS_clonesyscalls, mais dans le noyau, les sys_forket sys_clonesont à la fois des wrappers très fins autour de la même do_forkfonction, qui est elle-même un wrapper mince copy_process. Oui, les termes process, threadet tasksont utilisés de manière plutôt interchangeable dans le noyau Linux ...

éphémère
la source
6
Je pense qu'il nous manque 1 point. Si vous créez plusieurs processus pour votre serveur Web, vous devez alors écrire un autre processus pour ouvrir le socket et passer «work» à différents threads. Le filetage offre un processus unique à plusieurs fils, un design épuré. Dans de nombreuses situations, le fil est tout simplement naturel et dans d'autres situations, un nouveau processus est tout simplement naturel. Lorsque le problème tombe dans une zone grise, les autres compromis, comme expliqué par l'éphémient, deviennent importants.
Saurabh
26
@Saurabh Pas vraiment. Vous pouvez facilement socket, bind, listen, fork, et ont de multiples processus acceptconnexions sur la même prise d'écoute. Un processus peut cesser d'accepter s'il est occupé, et le noyau acheminera les connexions entrantes vers un autre processus (si personne n'écoute, le noyau sera mis en file d'attente ou abandonné, en fonction du listenretard). Vous n'avez pas beaucoup plus de contrôle sur la répartition du travail que cela, mais c'est généralement suffisant!
éphémère
2
@Bloodcount Tous les processus / threads sous Linux sont créés par le même mécanisme, qui clone un processus / thread existant. Indicateurs passés pour clone()déterminer quelles ressources sont partagées. Une tâche peut également unshare()ressources à tout moment ultérieur.
éphémère
4
@KarthikBalaguru Dans le noyau lui-même, il y a un task_structpour chaque tâche. Ceci est souvent appelé «processus» dans tout le code du noyau, mais il correspond à chaque thread exécutable. Il n'y a pas process_struct; si un tas de task_structs sont liés entre eux par leur thread_groupliste, alors ils sont le même "processus" à l'espace utilisateur. Il y a un peu de gestion spéciale des "threads", par exemple tous les threads frères sont arrêtés sur fork et exec, et seul le thread "principal" apparaît dans ls /proc. Chaque thread est accessible via /proc/pidcependant, qu'il soit répertorié /procou non.
éphémère
5
@KarthikBalaguru Le noyau prend en charge un continuum de comportement entre les threads et les processus; par exemple, clone(CLONE_THREAD | CLONE_VM | CLONE_SIGHAND))vous donnerait un nouveau "thread" qui ne partage pas le répertoire de travail, les fichiers ou les verrous, tandis clone(CLONE_FILES | CLONE_FS | CLONE_IO)que vous donnerait un "processus" qui le fait. Le système sous-jacent crée des tâches par clonage; fork()et ne pthread_create()sont que des fonctions de bibliothèque qui invoquent clone()différemment (comme je l'ai écrit dans cette réponse).
éphémère
60

Linux (et en effet Unix) vous offre une troisième option.

Option 1 - processus

Créez un exécutable autonome qui gère une partie (ou toutes les parties) de votre application, et invoquez-le séparément pour chaque processus, par exemple le programme exécute des copies de lui-même pour déléguer des tâches.

Option 2 - fils

Créez un exécutable autonome qui démarre avec un seul thread et créez des threads supplémentaires pour effectuer certaines tâches

Option 3 - fourche

Uniquement disponible sous Linux / Unix, c'est un peu différent. Un processus bifurqué est vraiment son propre processus avec son propre espace d'adressage - il n'y a rien que l'enfant puisse faire (normalement) pour affecter l'espace d'adressage de son parent ou de ses frères et sœurs (contrairement à un thread) - donc vous obtenez une robustesse supplémentaire.

Cependant, les pages mémoire ne sont pas copiées, elles sont copiées sur écriture, donc moins de mémoire est généralement utilisée que vous ne l'imaginez.

Considérez un programme de serveur Web qui se compose de deux étapes:

  1. Lire les données de configuration et d'exécution
  2. Servir les demandes de page

Si vous avez utilisé des threads, l'étape 1 serait effectuée une seule fois et l'étape 2 dans plusieurs threads. Si vous avez utilisé des processus "traditionnels", les étapes 1 et 2 devront être répétées pour chaque processus, et la mémoire pour stocker la configuration et les données d'exécution dupliquées. Si vous avez utilisé fork (), vous pouvez effectuer l'étape 1 une fois, puis fork (), en laissant les données d'exécution et la configuration en mémoire, intactes, non copiées.

Il y a donc vraiment trois choix.

MarkR
la source
7
@Qwertie forking n'est pas si cool, il casse de nombreuses bibliothèques de manière subtile (si vous les utilisez dans le processus parent). Il crée un comportement inattendu qui déroute même les programmeurs expérimentés.
MarkR
2
@MarkR pourriez-vous donner quelques exemples ou un lien sur la façon dont la fourche casse la bibliothèque et crée un comportement inattendu?
Ehtesh Choudhury
18
Si un processus bifurque avec une connexion mysql ouverte, de mauvaises choses se produisent, car le socket est partagé entre deux processus. Même si un seul processus utilise la connexion, l'autre l'empêche de se fermer.
MarkR
1
l'appel système fork () est spécifié par POSIX (ce qui signifie qu'il est disponible sur tous les systèmes Unix), si vous avez utilisé l'API Linux sous-jacente, qui est l'appel système clone (), vous avez en fait encore plus de choix sous Linux que les trois .
Lie Ryan
2
@MarkR Le partage de la socket se fait par conception. En outre, l'un ou l'autre des processus peut fermer le socket à l'aide de linux.die.net/man/2/shutdown avant d'appeler close () sur le socket.
Lelanthran
53

Cela dépend de nombreux facteurs. Les processus sont plus lourds que les threads et ont un coût de démarrage et d'arrêt plus élevé. La communication interprocessus (IPC) est également plus difficile et plus lente que la communication inter-fils.

Inversement, les processus sont plus sûrs et plus sécurisés que les threads, car chaque processus s'exécute dans son propre espace d'adressage virtuel. Si un processus se bloque ou a un dépassement de mémoire tampon, cela n'affecte aucun autre processus, alors que si un thread tombe en panne, il supprime tous les autres threads du processus et si un thread a un dépassement de mémoire tampon, il s'ouvre. un trou de sécurité dans tous les fils.

Donc, si les modules de votre application peuvent s'exécuter de manière indépendante avec peu de communication, vous devriez probablement utiliser des processus si vous pouvez vous permettre les coûts de démarrage et d'arrêt. Les performances d'IPC seront minimes et vous serez légèrement plus sûr contre les bugs et les failles de sécurité. Si vous avez besoin de toutes les performances que vous pouvez obtenir ou avoir beaucoup de données partagées (telles que des structures de données complexes), optez pour les threads.

Adam Rosenfield
la source
9
La réponse d'Adam servirait bien de briefing exécutif. Pour plus de détails, MarkR et ephemient fournissent de bonnes explications. Une explication très détaillée avec des exemples peut être trouvée sur cs.cf.ac.uk/Dave/C/node29.html mais elle semble être un peu datée en partie.
CyberFonic
2
CyberFonic est vrai pour Windows. Comme l'éphémient le dit sous Linux, les processus ne sont pas plus lourds. Et sous Linux, tous les mécanismes disponibles pour la communication entre les threads (futex, mémoire partagée, pipes, IPC) sont également disponibles pour les processus et s'exécutent à la même vitesse.
Russell Stuart
L'IPC est plus difficile à utiliser, mais que faire si quelqu'un utilise la «mémoire partagée»?
abhiarora
11

D'autres ont discuté des considérations.

Peut-être la différence importante est que dans Windows, les processus sont lourds et coûteux par rapport aux threads, et sous Linux, la différence est beaucoup plus petite, donc l'équation s'équilibre à un point différent.

dmckee --- chaton ex-modérateur
la source
9

Il était une fois Unix et dans ce bon vieux Unix il y avait beaucoup de surcharge pour les processus, donc ce que certaines personnes intelligentes ont fait était de créer des threads, qui partageraient le même espace d'adressage avec le processus parent et ils n'avaient besoin que d'un contexte réduit commutateur, ce qui rendrait le changement de contexte plus efficace.

Dans un Linux contemporain (2.6.x), il n'y a pas beaucoup de différence de performances entre un changement de contexte d'un processus par rapport à un thread (seul le truc MMU est supplémentaire pour le thread). Il y a le problème avec l'espace d'adressage partagé, ce qui signifie qu'un pointeur défectueux dans un thread peut corrompre la mémoire du processus parent ou un autre thread dans le même espace d'adressage.

Un processus est protégé par la MMU, donc un pointeur défectueux provoquera juste un signal 11 et aucune corruption.

J'utiliserais généralement des processus (pas beaucoup de surcharge de changement de contexte sous Linux, mais une protection de la mémoire en raison de MMU), mais pthreads si j'avais besoin d'une classe de planificateur en temps réel, ce qui est une tasse de thé différente tous ensemble.

Pourquoi pensez-vous que les threads ont un gain de performances aussi important sous Linux? Avez-vous des données à ce sujet, ou est-ce juste un mythe?

robert.berger
la source
1
Oui, j'ai des données. J'ai exécuté un test qui crée 100 000 processus et un test qui crée 100 000 threads. La version du thread s'exécutait environ 9 fois plus vite (17,38 secondes pour les processus, 1,93 pour les threads). Maintenant, cela ne teste que le temps de création, mais pour les tâches de courte durée, le temps de création peut être essentiel.
user17918
4
@ user17918 - Est-il possible pour vous de partager le code que vous avez utilisé pour calculer les horaires mentionnés ci-dessus ..
codingfreak
un grand différent, avec les processus, le noyau crée une table de pages pour chaque processus et les têtes n'utilisent qu'une seule table de pages, donc je pense qu'il est normal que les threads soient plus rapides que les processus
c4f4t0r
Un autre moyen simple de le voir est que TCB est assez petit que PCB et il est donc évident que le changement de contexte de processus qui implique PCB prendra un peu plus de temps que celui de la commutation des threads.
Karthik Balaguru
5

Dans quelle mesure vos tâches sont-elles étroitement liées?

S'ils peuvent vivre indépendamment les uns des autres, alors utilisez des processus. S'ils dépendent les uns des autres, utilisez des threads. De cette façon, vous pouvez tuer et redémarrer un mauvais processus sans interférer avec le fonctionnement des autres tâches.

Robert
la source
4

Pour compliquer encore les choses, il existe une chose telle que le stockage local par thread et la mémoire partagée Unix.

Le stockage local par thread permet à chaque thread d'avoir une instance distincte des objets globaux. La seule fois où je l'ai utilisé, c'était lors de la construction d'un environnement d'émulation sur linux / windows, pour du code d'application qui s'exécutait dans un RTOS. Dans le RTOS, chaque tâche était un processus avec son propre espace d'adressage, dans l'environnement d'émulation, chaque tâche était un thread (avec un espace d'adressage partagé). En utilisant TLS pour des choses comme les singletons, nous avons pu avoir une instance distincte pour chaque thread, tout comme dans le «vrai» environnement RTOS.

La mémoire partagée peut (évidemment) vous donner les avantages de performances d'avoir plusieurs processus accèdent à la même mémoire, mais au prix / risque d'avoir à synchroniser les processus correctement. Pour cela, un processus peut créer une structure de données dans la mémoire partagée, puis envoyer un descripteur à cette structure via une communication interprocessus traditionnelle (comme un canal nommé).

KeyserSoze
la source
1
J'ai utilisé le stockage local de threads pour une collecte de statistiques, la dernière fois que j'écrivais un programme de réseaux threadés: chaque thread a écrit sur ses propres compteurs, aucun verrou nécessaire, et uniquement lorsque messagé chaque thread combinait ses statistiques dans les totaux globaux. Mais oui, TLS n'est pas très utilisé ou nécessaire. La mémoire partagée, d'autre part ... en plus d'envoyer efficacement des données, vous pouvez également partager des sémaphores POSIX entre les processus en les plaçant dans la mémoire partagée. C'est assez étonnant.
éphémère
4

Dans mes travaux récents avec LINUX, il y a une chose à savoir, c'est les bibliothèques. Si vous utilisez des threads, assurez-vous que toutes les bibliothèques que vous pouvez utiliser sur plusieurs threads sont thread-safe. Cela m'a brûlé plusieurs fois. En particulier, libxml2 n'est pas thread-safe prêt à l'emploi. Il peut être compilé avec thread-safe mais ce n'est pas ce que vous obtenez avec l'installation d'aptitude.

aal8
la source
3

Je dois être d'accord avec ce que vous avez entendu. Lorsque nous comparons notre cluster ( xhplet autres), nous obtenons toujours de bien meilleures performances avec les processus sur les threads.</anecdote>

eduffy
la source
3

La décision entre thread / processus dépend un peu de l'utilisation que vous en ferez. L'un des avantages d'un processus est qu'il a un PID et peut être tué sans interrompre également le parent.

Pour un exemple réel de serveur Web, apache 1.3 ne supportait que plusieurs processus, mais en 2.0, ils ont ajouté une abstraction pour que vous puissiez basculer entre les deux. Commentaires semble à convenir que les processus sont plus robustes , mais les discussions peuvent donner un peu de meilleures performances (sauf pour les fenêtres où les performances des processus sucent et vous ne souhaitez que d'utiliser les threads).

hlovdal
la source
2

Dans la plupart des cas, je préférerais les processus aux threads. les threads peuvent être utiles lorsque vous avez une tâche relativement plus petite (surcharge de processus >> temps pris par chaque unité de travail divisée) et qu'il y a un besoin de partage de mémoire entre elles. Pensez à un large éventail. En outre (hors sujet), notez que si l'utilisation de votre processeur est à 100% ou proche de celle-ci, le multithreading ou le traitement ne retireront aucun avantage. (en fait ça va empirer)

neal aise
la source
Que voulez-vous dire aucun avantage? Que diriez-vous d'effectuer des calculs lourds dans le thread GUI? Les déplacer vers un thread parallèle sera beaucoup mieux du point de vue de l'utilisateur, quelle que soit la charge du processeur.
2015
2

Threads -> Threads partage un espace mémoire, c'est une abstraction du CPU, il est léger. Processus -> Les processus ont leur propre espace mémoire, c'est une abstraction d'un ordinateur. Pour paralléliser la tâche, vous devez abstraire un processeur. Cependant, les avantages de l'utilisation d'un processus sur un thread sont la sécurité, la stabilité tandis qu'un thread utilise moins de mémoire que le processus et offre une latence moindre. Un exemple en termes de web serait chrome et firefox. Dans le cas de Chrome, chaque onglet est un nouveau processus, donc l'utilisation de la mémoire de Chrome est supérieure à celle de Firefox, tandis que la sécurité et la stabilité fournies sont meilleures que celles de Firefox. La sécurité ici fournie par Chrome est meilleure, car chaque onglet est un nouvel onglet de processus différent ne peut pas espionner dans l'espace mémoire d'un processus donné.

Jubin Antony Thykattil
la source
2

Je pense que tout le monde a fait un excellent travail en répondant à votre question. J'ajoute simplement plus d'informations sur le thread par rapport au processus sous Linux pour clarifier et résumer certaines des réponses précédentes dans le contexte du noyau. Donc, ma réponse concerne le code spécifique au noyau sous Linux. Selon la documentation du noyau Linux, il n'y a pas de distinction claire entre thread et processus, sauf que le thread utilise un espace d'adressage virtuel partagé contrairement au processus. Notez également que le noyau Linux utilise le terme «tâche» pour faire référence au processus et au thread en général.

"Il n'y a pas de structures internes implémentant des processus ou des threads, mais une structure task_struct décrivant une unité de planification abstraite appelée tâche"

Toujours selon Linus Torvalds, vous ne devriez PAS du tout penser au processus par rapport au thread et parce que c'est trop limitatif et la seule différence est le COE ou le contexte d'exécution en termes de "séparation de l'espace d'adressage du parent" ou d'espace d'adressage partagé. En fait, il utilise un exemple de serveur Web pour faire valoir son point de vue ici (qui recommande fortement la lecture).

Crédit complet à la documentation du noyau Linux

grepit
la source
-3

Si vous devez partager des ressources, vous devez vraiment utiliser des threads.

Considérez également le fait que les changements de contexte entre les threads sont beaucoup moins chers que les changements de contexte entre les processus.

Je ne vois aucune raison d'aller explicitement avec des processus séparés, sauf si vous avez une bonne raison de le faire (sécurité, tests de performances éprouvés, etc ...)

Yuval Adam
la source
3
J'ai le représentant à éditer, mais je ne suis pas tout à fait d'accord. Les changements de contexte entre les processus sous Linux sont presque aussi bon marché que les changements de contexte entre les threads.
éphémère