Processus, fils, fils verts, protothreads, fibres, coroutines: quelle est la différence?

Je lis sur la concurrence. J’ai un peu plus de tête avec des termes qui ont des définitions similaires. À savoir:

  • Processus
  • Des filets
  • “Les fils verts”
  • Filets Protothiques
  • Des fibres
  • Coroutines
  • “Goroutines” dans la langue de Go

Mon impression est que les distinctions reposent sur (1) si elles sont vraiment parallèles ou multiplexées; (2) qu’ils soient gérés au niveau du processeur, du système d’exploitation ou du programme; et (3..5) quelques autres choses que je ne peux pas identifier.

Existe-t-il un guide succinct et non ambigu des différences entre ces approches du parallélisme?

OK, je vais faire de mon mieux. Il y a des mises en garde partout, mais je vais faire de mon mieux pour donner ma compréhension de ces termes et références à quelque chose qui se rapproche de la définition que j’ai donnée.

  • Processus : géré par le système d’exploitation (éventuellement) réellement simultané, au moins en présence d’un support matériel approprié. Existe dans leur propre espace d’adressage.
  • Thread : géré par le système d’exploitation, dans le même espace d’adressage que le parent et tous ses autres threads. Peut-être vraiment simultané, et le multitâche est préemptif.
  • Fil vert : Ce sont des projections de l’espace utilisateur du même concept que les threads, mais elles ne sont pas gérées par le système d’exploitation. Probablement pas vraiment concurrente, sauf dans le sens où il peut y avoir plusieurs threads de travail ou processus leur donnant du temps CPU en même temps, il est donc préférable de considérer ceci comme entrelacé ou multiplexé.
  • Protothreads : Je ne pouvais pas vraiment en tirer une définition. Je pense qu’ils sont entrelacés et gérés par programme, mais ne le croyez pas. À mon sens, ils sont essentiellement une implémentation spécifique à une application du même type de modèle de «threads verts», avec des modifications appropriées pour le domaine d’application.
  • Fibres : gérées par OS. Exactement les threads, sauf le multitâche en coopération, et donc pas vraiment simultanés.
  • Coroutines : Fibres exactes , sauf pas gérées par OS.
  • Goroutines : Ils prétendent être différents de tout, mais ils semblent être exactement des fils verts, comme dans, gérés par processus dans un espace d’adressage unique et multiplexés sur les threads du système. Peut-être quelqu’un avec plus de connaissances de Go peut-il trancher le matériel de marketing.

Il convient également de noter que la théorie de la simultanéité du terme “processus” comprend d’autres notions dans le processus de calcul des processus . Cette définition est orthogonale à celles ci-dessus, mais je pensais que cela valait la peine de le mentionner afin qu’aucune confusion ne se produise si vous voyiez un processus utilisé dans ce sens quelque part.

Aussi, soyez conscient de la différence entre parallèle et simultané . Il est possible que vous utilisiez le premier dans votre question où je pense que vous vouliez dire le dernier.

Je suis principalement d’accord avec la réponse de Gian, mais j’ai des interprétations différentes de quelques primitives de concurrence. Notez que ces termes sont souvent utilisés de manière incohérente par différents auteurs. Ce sont mes définitions préférées (heureusement pas trop loin du consensus moderne).

  • Processus:
    • Géré par OS
    • Chacun a son propre espace d’adressage virtuel
    • Peut être interrompu (préempté) par le système pour permettre à un autre processus de s’exécuter
    • Peut être exécuté en parallèle avec d’autres processus sur différents processeurs
    • La surcharge de mémoire des processus est élevée (y compris les tables de mémoire virtuelle, les descripteurs de fichiers ouverts, etc.)
    • La surcharge de temps pour la création et le changement de contexte entre les processus est relativement élevée
  • Threads:
    • Géré par OS
    • Chacun est “contenu” dans un processus particulier
    • Tous les threads du même processus partagent le même espace d’adressage virtuel
    • Peut être interrompu par le système pour permettre à un autre thread de s’exécuter
    • Peut être exécuté en parallèle avec d’autres threads sur différents processeurs
    • Les surcharges de mémoire et de temps associées aux threads sont plus petites que les processus, mais ne sont pas sortingviales
      • (Par exemple, le changement de contexte consiste généralement à entrer le kernel et à appeler le planificateur système.)
  • Fils de coopération:
    • Peut ou non être géré par le système d’exploitation
    • Chacun est “contenu” dans un processus particulier
    • Dans certaines implémentations, chacune est “contenue” dans un thread de système d’exploitation particulier
    • Ne peut pas être interrompu par le système pour permettre à un pair coopératif de s’exécuter
      • (Le processus / thread contenant peut encore être interrompu, bien sûr)
    • Doit invoquer une primitive de rendement spéciale pour permettre aux threads coopératifs de s’exécuter
    • En règle générale, ne peut pas être exécuté en parallèle avec les pairs coopératifs
    • Il y a beaucoup de variations sur le thème du thread coopératif qui portent des noms différents:
      • Des fibres
      • Fils verts
      • Filets Protothiques
      • Threads de niveau utilisateur (les threads au niveau utilisateur peuvent être interrompus / préemptifs, mais c’est une combinaison relativement inhabituelle)
    • Certaines implémentations de threads coopératifs utilisent des techniques telles que les stacks fractionnées / segmentées ou même l’allocation individuelle de tas à chaque trame d’appel pour réduire la surcharge de mémoire associée à la pré-allocation d’une grande quantité de mémoire pour la stack.
    • Selon l’implémentation, l’appel d’un appel syscall bloquant (comme la lecture du réseau ou la mise en veille) provoquera le blocage de tout un groupe de threads coopératifs ou provoquera implicitement le thread appelant.
  • Coroutines:
    • Certaines personnes utilisent “coroutine” et “coopérative” plus ou moins synonyme
      • Je ne préfère pas cet usage
    • Certaines implémentations de coroutines sont en réalité des threads coopératifs peu profonds; le rendement ne peut être invoqué que par la “procédure d’entrée de coroutine”
    • La version peu profonde (ou semi-coroutine) est plus facile à implémenter que les threads, car chaque coroutine n’a pas besoin d’une stack complète (juste une image pour la procédure de saisie)
    • Souvent, les frameworks de coroutine ont des primitives de rendement qui exigent que l’invocateur indique explicitement à quel contrôle de coroutine
  • Générateurs:
    • Coroutines restreintes (peu profondes)
    • le rendement ne peut que ramener le contrôle au code invoqué par le générateur
  • Goroutines:
    • Un hybride étrange de threads coopératifs et OS
    • Ne peut pas être interrompu (comme les fils coopératifs)
    • Peut être exécuté en parallèle sur un pool de threads de système d’exploitation géré par runtime en langage
  • Gestionnaires d’événements:
    • Procédures / méthodes appelées par un répartiteur d’événements en réponse à une action en cours
    • Très populaire pour la programmation d’interface utilisateur
    • Requiert peu ou pas de prise en charge de la langue / du système; peut être implémenté dans une bibliothèque
    • Au plus, un gestionnaire d’événements peut être exécuté à la fois. le répartiteur doit attendre qu’un gestionnaire se termine (retour) avant de commencer le suivant
      • Rend la synchronisation relativement simple différentes exécutions de gestionnaires ne se chevauchent jamais dans le temps
    • L’implémentation de tâches complexes avec des gestionnaires d’événements a tendance à conduire à un “stream de contrôle inversé” / “extraction de stack”
  • Les tâches:
    • Unités de travail dissortingbuées par un responsable à un groupe de travailleurs
    • Les travailleurs peuvent être des fils, des processus ou des machines
      • Bien sûr, le type de travailleur utilisé par une bibliothèque de tâches a un impact significatif sur la manière dont les tâches sont implémentées.
    • Dans cette liste de termes utilisés de manière incohérente et confuse, “tâche” prend la couronne. Particulièrement dans la communauté des systèmes embarqués, “tâche” est parfois utilisé pour signifier “processus”, “thread” ou “gestionnaire d’événements” (généralement appelé “routine de service d’interruption”). Il est aussi parfois utilisé de manière générique / informelle pour désigner tout type d’unité de calcul.

Une bête noire que je ne peux pas m’empêcher de diffuser: je n’aime pas l’utilisation de l’expression “véritable concurrence” pour le “parallélisme du processeur”. C’est assez commun, mais je pense que cela entraîne beaucoup de confusion.

Pour la plupart des applications, je pense que les frameworks basés sur des tâches sont les meilleurs pour la parallélisation. La plupart des logiciels populaires (TBB d’Intel, GCD d’Apple, TPL et PPL de Microsoft) utilisent des threads comme travailleurs. Je souhaite qu’il y ait de bonnes alternatives utilisant des processus, mais je n’en connais aucun.

Si vous êtes intéressé par la concurrence (par opposition au parallélisme du processeur), les gestionnaires d’événements sont le moyen le plus sûr d’aller de l’avant. Les fils coopératifs sont une alternative intéressante, mais un peu sauvage. S’il vous plaît ne pas utiliser les threads pour la concurrence si vous vous souciez de la fiabilité et de la robustesse de votre logiciel.

Les Protothreads ne sont qu’une implémentation de boîtier de commutation qui agit comme une machine à états, mais simplifie considérablement l’implémentation du logiciel. Il est basé sur l’idée de sauvegarder une valeur et une valeur int avant une étiquette de casse et de retourner puis de revenir au point après la casse en relisant cette variable et en utilisant switch pour déterminer où continuer. Donc, protothread est une implémentation séquentielle d’une machine à états.

Les protothreads sont parfaits lors de la mise en œuvre de machines à états séquentiels. Les Protothreads ne sont pas du tout des threads, mais plutôt une abstraction syntaxique qui facilite beaucoup l’écriture d’une machine à états de commutateurs / états qui doit changer d’états de manière séquentielle (de l’un à l’autre, etc.).

J’ai utilisé des protothreads pour implémenter io asynchrone: http://martinschroder.se/asynchronous-io-using-protothreads/