web-dev-qa-db-fra.com

Les piscines de goroutine go-langs sont-elles juste des fils verts?

Le commentateur ici offre la critique suivante des fils verts:

J'ai d'abord été vendu sur le modèle N: M comme moyen d'avoir une programmation événementielle sans l'enfer de rappel. Vous pouvez écrire du code qui ressemble à du vieux code procédural mais en dessous il y a de la magie qui utilise la commutation des tâches de l'espace utilisateur chaque fois que quelque chose se bloque. Super. Le problème est que nous finissons par résoudre la complexité avec plus de complexité. swapcontext () et la famille sont assez étroits, la complexité vient d'autres endroits involontaires.

Tout d'un coup, vous êtes obligé d'écrire un planificateur d'espace utilisateur et devinez ce qu'il est vraiment difficile d'écrire un planificateur qui fera un meilleur travail que les planifications de Linux qui ont des années-hommes d'efforts. Maintenant, vous voulez que votre planning gère N threads verts sur M threads physiques, vous devez donc vous soucier de la synchronisation. La synchronisation pose des problèmes de performances, vous commencez donc maintenant que vous êtes dans un nouveau trou de lapin sans verrou. Construire un ordonnanceur hautement simultané correct n'est pas une tâche facile.

Un autre la critique est ici :

Un processus unique simulant plusieurs threads a beaucoup de problèmes. L'un d'eux est que tous les fils truqués bloquent sur n'importe quel défaut de page.

Ma question est - sont les goroutines de go-lang (pour un pool par défaut) juste des fils verts? Si oui - répondent-ils aux critiques ci-dessus?

50
hawkeye

Je ne suis qu'un utilisateur occasionnel de Go, alors prenez ce qui suit avec un grain de sel.

Wikipedia définit fils verts comme "les fils qui sont planifiés par une machine virtuelle (VM) au lieu de nativement par le système d'exploitation sous-jacent". Les threads verts émulent des environnements multithreads sans compter sur des capacités de système d'exploitation natif, et ils sont gérés dans l'espace utilisateur au lieu de l'espace du noyau, ce qui leur permet de travailler dans des environnements qui ne prennent pas en charge les threads natifs.

Go (ou plus exactement les deux implémentations existantes) est un langage qui produit uniquement du code natif - il n'utilise pas de machine virtuelle. En outre, le planificateur dans les implémentations d'exécution actuelles repose sur des threads au niveau du système d'exploitation (même lorsque GOMAXPROCS = 1). Je pense donc que parler de fils verts pour le modèle Go est un peu abusif.

Les gens de Go ont inventé le terme de goroutine spécialement pour éviter la confusion avec d'autres mécanismes de concurrence (comme les coroutines ou les threads ou les processus légers).

Bien sûr, Go prend en charge un modèle de thread M: N, mais il ressemble beaucoup plus au modèle de processus Erlang qu'au modèle de thread vert Java).

Voici quelques avantages du modèle Go par rapport aux fils verts (mis en œuvre dans les premières JVM):

  • Plusieurs cœurs ou processeurs peuvent être utilisés efficacement, de manière transparente pour le développeur. Avec Go, le développeur doit s'occuper de la concurrence. Le runtime Go prendra en charge le parallélisme. Java ne se sont pas étendues sur plusieurs cœurs ou processeurs.

  • Les appels système et C ne bloquent pas le planificateur (tous les appels système, pas seulement ceux qui prennent en charge les E/S multiplexées dans les boucles d'événement). Les implémentations de threads verts peuvent bloquer l'ensemble du processus lorsqu'un appel système de blocage est effectué.

  • Copie ou piles segmentées. Dans Go, il n'est pas nécessaire de fournir une taille de pile maximale pour le goroutine. La pile augmente progressivement selon les besoins. Une conséquence est qu'un goroutine ne nécessite pas beaucoup de mémoire (4KB-8KB), donc un grand nombre d'entre eux peuvent être générés avec bonheur. L'utilisation de la goroutine peut donc être omniprésente.

Maintenant, pour répondre aux critiques:

  • Avec Go, vous n'avez pas besoin d'écrire un planificateur d'espace utilisateur: il est déjà fourni avec le runtime. C'est un logiciel complexe, mais c'est le problème des développeurs Go, pas des utilisateurs Go. Son utilisation est transparente pour les utilisateurs de Go. Parmi les développeurs de Go, Dmitri Vyukov est un expert en programmation sans verrouillage/sans attente, et il semble être particulièrement intéressé à résoudre les éventuels problèmes de performances du planificateur. L'implémentation actuelle du planificateur n'est pas parfaite, mais elle s'améliorera.

  • La synchronisation entraîne des problèmes de performances et de la complexité: cela est également vrai en partie avec Go. Mais notez que le modèle Go essaie de promouvoir l'utilisation des canaux et une décomposition propre du programme dans des goroutines simultanées pour limiter la complexité de la synchronisation (c'est-à-dire partager des données en communiquant, au lieu de partager de la mémoire pour communiquer). Soit dit en passant, l'implémentation Go de référence fournit un certain nombre d'outils pour résoudre les problèmes de performances et de concurrence, comme un profileur et un détecteur de course .

  • En ce qui concerne les erreurs de page et la simulation de plusieurs threads, veuillez noter que Go peut planifier goroutine sur plusieurs threads système. Lorsqu'un thread est bloqué pour une raison quelconque (défaut de page, blocage des appels système), cela n'empêche pas les autres threads de continuer à planifier et à exécuter d'autres goroutines. Maintenant, il est vrai qu'une erreur de page bloquera le thread OS, avec tous les goroutines censés être planifiés sur ce thread. Cependant, dans la pratique, la mémoire de tas Go n'est pas censée être remplacée. Ce serait la même chose en Java: les langages récupérés n'acceptent pas très bien la mémoire virtuelle de toute façon. Si votre programme doit gérer les défauts de page de manière gracieuse, si c'est probablement parce qu'il doit gérer de la mémoire hors tas. Dans ce cas, encapsuler le code correspondant avec des fonctions d'accesseur C résoudra simplement le problème (car encore une fois les appels C ou les appels système bloquants ne bloquent jamais le planificateur d'exécution Go).

Donc, l'OMI, les goroutines ne sont pas des fils verts, et le langage Go et l'implémentation actuelle répondent principalement à ces critiques.

69
Didier Spezia