Was genau macht runtime.Gosched?


84

In einer Version vor der Veröffentlichung von go 1.5 der Tour of Go-Website gibt es einen Code, der so aussieht.

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

Die Ausgabe sieht folgendermaßen aus:

hello
world
hello
world
hello
world
hello
world
hello

Was mich stört ist, dass runtime.Gosched()das Programm beim Entfernen nicht mehr "Welt" druckt.

hello
hello
hello
hello
hello

Warum ist das so? Wie wirkt sich das runtime.Gosched()auf die Ausführung aus?

Antworten:


141

Hinweis:

Ab Go 1.5 ist GOMAXPROCS auf die Anzahl der Kerne der Hardware eingestellt: golang.org/doc/go1.5#runtime , unterhalb der ursprünglichen Antwort vor 1.5.


Wenn Sie das Go-Programm ausführen, ohne die Umgebungsvariable GOMAXPROCS anzugeben, werden Go-Goroutinen für die Ausführung in einem einzelnen Betriebssystem-Thread geplant. Um das Programm jedoch als Multithread-Programm erscheinen zu lassen (dafür sind Goroutinen gedacht, nicht wahr?), Muss der Go-Scheduler manchmal den Ausführungskontext wechseln, damit jede Goroutine ihre Arbeit erledigen kann.

Wie gesagt, wenn die GOMAXPROCS-Variable nicht angegeben ist, darf die Go-Laufzeit nur einen Thread verwenden, sodass es unmöglich ist, den Ausführungskontext zu wechseln, während Goroutine einige konventionelle Arbeiten wie Berechnungen oder sogar E / A ausführt (die einfachen C-Funktionen zugeordnet sind) ). Der Kontext kann nur umgeschaltet werden, wenn Go-Parallelitätsprimitive verwendet werden, z. B. wenn Sie mehrere Chans einschalten oder (dies ist Ihr Fall) wenn Sie den Scheduler ausdrücklich anweisen, die Kontexte zu wechseln - dafür ist dies vorgesehen runtime.Gosched.

Kurz gesagt, wenn der Ausführungskontext in einer Goroutine den GoschedAufruf erreicht , wird der Scheduler angewiesen, die Ausführung auf eine andere Goroutine umzuschalten. In Ihrem Fall gibt es zwei Goroutinen, main (der 'Haupt'-Thread des Programms darstellt) und zusätzlich die, mit der Sie erstellt haben go say. Wenn Sie den GoschedAnruf entfernen , wird der Ausführungskontext niemals von der ersten Goroutine zur zweiten übertragen, daher keine "Welt" für Sie. Wenn Goschedvorhanden, überträgt der Scheduler die Ausführung bei jeder Schleifeniteration von der ersten Goroutine zur zweiten und umgekehrt, sodass Sie "Hallo" und "Welt" verschachtelt haben.

Zu Ihrer Information, dies wird als "kooperatives Multitasking" bezeichnet: Goroutinen müssen die Kontrolle explizit anderen Goroutinen überlassen. Der in den meisten modernen Betriebssystemen verwendete Ansatz wird als "präemptives Multitasking" bezeichnet: Ausführungsthreads befassen sich nicht mit der Übertragung von Steuerelementen. Der Scheduler wechselt stattdessen transparent zu ihnen die Ausführungskontexte. Ein kooperativer Ansatz wird häufig verwendet, um "grüne Threads" zu implementieren, dh logische gleichzeitige Coroutinen, die OS-Threads nicht 1: 1 zuordnen. Auf diese Weise werden die Go-Laufzeit und ihre Goroutinen implementiert.

Aktualisieren

Ich habe die Umgebungsvariable GOMAXPROCS erwähnt, aber nicht erklärt, was es ist. Es ist Zeit, dies zu beheben.

Wenn diese Variable auf eine positive Zahl gesetzt ist N, kann Go Runtime bis zu Nnative Threads erstellen , auf denen alle grünen Threads geplant werden. Nativer Thread Eine Art Thread, der vom Betriebssystem erstellt wird (Windows-Threads, Pthreads usw.). Dies bedeutet, Ndass Goroutinen , wenn sie größer als 1 sind, möglicherweise in verschiedenen nativen Threads ausgeführt werden und folglich parallel ausgeführt werden (zumindest bis zu Ihren Computerfähigkeiten: Wenn Ihr System auf einem Multicore-Prozessor basiert, ist dies der Fall Es ist wahrscheinlich, dass diese Threads wirklich parallel sind. Wenn Ihr Prozessor über einen einzelnen Kern verfügt, führt das in OS-Threads implementierte präemptive Multitasking zu einer Sichtbarkeit der parallelen Ausführung.

Es ist möglich, die Variable GOMAXPROCS mithilfe der runtime.GOMAXPROCS()Funktion festzulegen, anstatt die Umgebungsvariable voreinzustellen . Verwenden Sie in Ihrem Programm so etwas anstelle des aktuellen main:

func main() {
    runtime.GOMAXPROCS(2)
    go say("world")
    say("hello")
}

In diesem Fall können Sie interessante Ergebnisse beobachten. Es ist möglich, dass Sie "Hallo" - und "Welt" -Linien ungleichmäßig verschachtelt drucken lassen, z

hello
hello
world
hello
world
world
...

Dies kann passieren, wenn Goroutinen OS-Threads trennen sollen. So funktioniert präventives Multitasking (oder Parallelverarbeitung bei Multicore-Systemen): Threads sind parallel und ihre kombinierte Ausgabe ist unbestimmt. Übrigens, Sie können den GoschedAnruf verlassen oder entfernen . Es scheint keine Auswirkung zu haben, wenn GOMAXPROCS größer als 1 ist.

Folgendes habe ich bei mehreren Programmläufen mit runtime.GOMAXPROCSAufruf erhalten.

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

Sehen Sie, manchmal ist die Ausgabe hübsch, manchmal nicht. Indeterminismus in Aktion :)

Ein weiteres Update

In neueren Versionen des Go-Compilers sieht die Go-Laufzeit aus, dass Goroutinen nicht nur bei der Verwendung von Parallelitätsprimitiven, sondern auch bei Systemaufrufen des Betriebssystems nachgeben. Dies bedeutet, dass der Ausführungskontext auch bei E / A-Funktionsaufrufen zwischen Goroutinen umgeschaltet werden kann. Folglich ist es in neueren Go-Compilern möglich, unbestimmtes Verhalten zu beobachten, selbst wenn GOMAXPROCS nicht gesetzt oder auf 1 gesetzt ist.


Gut gemacht ! Aber ich habe dieses Problem unter go 1.0.3 nicht kennengelernt, komisch.
WoooHaaaa

1
Das ist wahr. Ich habe dies gerade mit go 1.0.3 überprüft, und ja, dieses Verhalten ist nicht aufgetreten: Selbst mit GOMAXPROCS == 1 hat das Programm so funktioniert, als ob GOMAXPROCS> = 2. In 1.0.3 wurde der Scheduler optimiert.
Vladimir Matveev

Ich denke, die Dinge haben sich gegenüber dem 1.4 Compiler geändert. Beispiel in OPs Frage scheint das Erstellen von Betriebssystem-Threads zu sein, während dies (-> gobyexample.com/atomic-counters ) eine kooperative Planung zu erstellen scheint. Bitte aktualisieren Sie die Antwort, wenn dies wahr ist
tez

8
Ab Go 1.5 ist GOMAXPROCS auf die Anzahl der Kerne der Hardware eingestellt: golang.org/doc/go1.5#runtime
thepanuto

1
@paulkon, ob Gosched()benötigt wird oder nicht , hängt von Ihrem Programm ab, es hängt nicht vom GOMAXPROCSWert ab. Die Effizienz des präventiven Multitasking gegenüber dem kooperativen hängt auch von Ihrem Programm ab. Wenn Ihr Programm E / A-gebunden ist, ist kooperatives Multitasking mit asynchronen E / A wahrscheinlich effizienter (dh hat einen höheren Durchsatz) als synchrone Thread-basierte E / A. Wenn Ihr Programm CPU-gebunden ist (z. B. lange Berechnungen), ist kooperatives Multitasking viel weniger nützlich.
Vladimir Matveev

7

Die kooperative Planung ist der Schuldige. Ohne nachzugeben, kann die andere (sagen wir "Welt") Goroutine legal keine Chance haben, vor / nach dem Ende von main ausgeführt zu werden, was laut Spezifikation alle Gorutinen beendet - dh. der ganze Prozess.


1
Okay, also runtime.Gosched()gibt nach. Was bedeutet das? Gibt es die Steuerung zurück zur Hauptfunktion?
Jason Yeo

5
In diesem speziellen Fall ja. Im Allgemeinen wird der Planer aufgefordert, eine der "bereiten" Goroutinen in einer absichtlich nicht festgelegten Auswahlreihenfolge einzuschalten und auszuführen.
zzzz
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.