Remarque:
À partir de Go 1.5, GOMAXPROCS est défini sur le nombre de cœurs du matériel: golang.org/doc/go1.5#runtime , en dessous de la réponse d'origine avant 1.5.
Lorsque vous exécutez le programme Go sans spécifier la variable d'environnement GOMAXPROCS, les routines Go sont planifiées pour une exécution dans un seul thread OS. Cependant, pour donner l'impression que le programme est multithread (c'est à cela que servent les goroutines, n'est-ce pas?), Le planificateur Go doit parfois changer de contexte d'exécution, afin que chaque goroutine puisse faire son travail.
Comme je l'ai dit, lorsque la variable GOMAXPROCS n'est pas spécifiée, le runtime Go n'est autorisé à utiliser qu'un seul thread, il est donc impossible de changer de contexte d'exécution pendant que goroutine effectue un travail conventionnel, comme des calculs ou même IO (qui est mappé à des fonctions C simples ). Le contexte ne peut être changé que lorsque les primitives de concurrence Go sont utilisées, par exemple lorsque vous activez plusieurs canaux, ou (c'est votre cas) lorsque vous dites explicitement au planificateur de changer les contextes - c'est à cela que runtime.Gosched
sert.
Donc, en bref, lorsque le contexte d'exécution dans un goroutine atteint l' Gosched
appel, le planificateur est chargé de basculer l'exécution sur un autre goroutine. Dans votre cas, il y a deux goroutines, main (qui représente le thread «principal» du programme) et supplémentaire, celle avec laquelle vous avez créé go say
. Si vous supprimez l' Gosched
appel, le contexte d'exécution ne sera jamais transféré de la première goroutine à la seconde, donc pas de «monde» pour vous. Quand Gosched
est présent, l'ordonnanceur transfère l'exécution à chaque itération de boucle de la première goroutine à la seconde et vice versa, donc vous avez entrelacé «bonjour» et «monde».
Pour info, cela s'appelle le «multitâche coopératif»: les goroutines doivent explicitement céder le contrôle aux autres goroutines. L'approche utilisée dans la plupart des systèmes d'exploitation contemporains est appelée «multitâche préemptif»: les threads d'exécution ne sont pas concernés par le transfert de contrôle; le planificateur change les contextes d'exécution de manière transparente vers eux à la place. L'approche coopérative est fréquemment utilisée pour implémenter des `` threads verts '', c'est-à-dire des coroutines logiques concurrentes qui ne mappent pas 1: 1 aux threads du système d'exploitation - c'est ainsi que le runtime Go et ses goroutines sont implémentés.
Mise à jour
J'ai mentionné la variable d'environnement GOMAXPROCS mais je n'ai pas expliqué de quoi il s'agissait. Il est temps de résoudre ce problème.
Lorsque cette variable est définie sur un nombre positif N
, le runtime Go pourra créer jusqu'à N
des threads natifs, sur lesquels tous les threads verts seront planifiés. Thread natif une sorte de thread qui est créé par le système d'exploitation (threads Windows, pthreads, etc.). Cela signifie que si N
est supérieur à 1, il est possible que les goroutines soient planifiées pour s'exécuter dans différents threads natifs et, par conséquent, s'exécuter en parallèle (au moins, à la hauteur des capacités de votre ordinateur: si votre système est basé sur un processeur multicœur, il est probable que ces threads seront vraiment parallèles; si votre processeur a un seul cœur, alors le multitâche préemptif implémenté dans les threads du système d'exploitation créera une visibilité de l'exécution parallèle).
Il est possible de définir la variable GOMAXPROCS en utilisant la runtime.GOMAXPROCS()
fonction au lieu de prérégler la variable d' environnement. Utilisez quelque chose comme ceci dans votre programme au lieu du courant main
:
func main() {
runtime.GOMAXPROCS(2)
go say("world")
say("hello")
}
Dans ce cas, vous pouvez observer des résultats intéressants. Il est possible que vous obteniez des lignes «bonjour» et «monde» imprimées de manière inégale, par exemple
hello
hello
world
hello
world
world
...
Cela peut arriver si les goroutines sont planifiées pour séparer les threads du système d'exploitation. C'est en fait ainsi que fonctionne le multitâche préemptif (ou le traitement parallèle dans le cas des systèmes multicœurs): les threads sont parallèles et leur sortie combinée est indéterministe. BTW, vous pouvez quitter ou supprimer un Gosched
appel, cela semble n'avoir aucun effet lorsque GOMAXPROCS est supérieur à 1.
Ce qui suit est ce que j'ai obtenu sur plusieurs exécutions du programme avec runtime.GOMAXPROCS
appel.
hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
Voir, parfois la sortie est jolie, parfois non. Indéterminisme en action :)
Une autre mise à jour
On dirait que dans les nouvelles versions du compilateur Go Go runtime force les goroutines à céder non seulement sur l'utilisation des primitives de concurrence, mais aussi sur les appels système du système d'exploitation. Cela signifie que le contexte d'exécution peut être commuté entre les goroutines également sur les appels de fonctions IO. Par conséquent, dans les compilateurs Go récents, il est possible d'observer un comportement indéterministe même lorsque GOMAXPROCS n'est pas défini ou défini sur 1.