2012-10-28 26 views
56

W a version prior to the release of go 1.5 of the Tour of Go website jest fragment kodu, który wygląda tak.Co dokładnie robi plik runtime.Gosched?

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") 
} 

Wyjście wygląda następująco:

hello 
world 
hello 
world 
hello 
world 
hello 
world 
hello 

Co przeszkadza mi to, że gdy runtime.Gosched() zostanie usunięta, nie jest już program wypisuje „świat”.

hello 
hello 
hello 
hello 
hello 

Dlaczego tak jest? W jaki sposób runtime.Gosched() wpływa na wykonanie?

Odpowiedz

94

Po uruchomieniu programu Go bez określania zmiennej środowiskowej GOMAXPROCS program Go goroutines jest zaplanowany do wykonania w jednym wątku systemu operacyjnego. Jednak, aby program wyglądał na wielowątkowy (do czego służą goroutiny, prawda?), Harmonogram Go musi czasami zmieniać kontekst wykonania, aby każdy goroutine mógł wykonać swoją pracę.

Jak już wspomniałem, gdy zmienna GOMAXPROCS nie jest określona, ​​środowisko wykonawcze Go może używać tylko jednego wątku, więc niemożliwe jest przełączanie kontekstów wykonania, podczas gdy goroutine wykonuje niektóre konwencjonalne prace, takie jak obliczenia lub nawet IO (który jest mapowany do prostych funkcji C). Kontekst można przełączać tylko wtedy, gdy używane są prymitywy Go concurrency, np. po włączeniu kilku kanałów lub (tak jest w twoim przypadku), gdy wyraźnie powiadamiasz program planujący o przełączeniu kontekstów - właśnie do tego służy runtime.Gosched.

Krótko mówiąc, gdy kontekst wykonania w jednej goroutine osiąga Gosched, program planujący jest polecany, aby przełączyć wykonanie na inną goroutine. W twoim przypadku są dwie goroutiny, główna (która reprezentuje "główny" wątek programu) i dodatkowy, ten, który utworzyłeś z go say. Jeśli usuniesz wywołanie Gosched, kontekst wykonania nigdy nie zostanie przeniesiony z pierwszej goroutine do drugiej, a więc nie będzie dla ciebie "świata". Gdy obecny jest Gosched, program planujący przekazuje wykonanie każdej iteracji pętli od pierwszej goroutine do drugiej i odwrotnie, więc masz "cześć" i "świat" przeplatane.

FYI, to się nazywa "wielozadaniowość kooperacyjna": goryle muszą jawnie poddać kontrolę innym goroutinom. Podejście stosowane w większości współczesnych systemów operacyjnych nosi nazwę "wielozadaniowości wyprzedzającej": wątki wykonawcze nie dotyczą przekazywania kontroli; program planujący zamiast tego przełącza konteksty wykonania zamiast nich. Podejście oparte na współpracy jest często stosowane do implementacji "zielonych wątków", czyli logicznych współbieżnych współprogramów, które nie mapują wątków 1: 1 na system operacyjny - tak właśnie wdrażane są środowisko wykonawcze Go i jego goroutines.

Aktualizacja

wspominałem zmienną środowiskową GOMAXPROCS ale nie wyjaśnia, co to jest. Czas to naprawić.

Gdy ta zmienna jest ustawiona na liczbę dodatnią N, aplikacja runtime Go będzie w stanie utworzyć do N natywnych wątków, na których zaplanowane będą wszystkie wątki zielone. Wątek rodzimy to rodzaj wątku tworzonego przez system operacyjny (wątki Windows, pthreads itp.).Oznacza to, że jeśli N jest większe niż 1, możliwe jest, że goroutines będą planowane do wykonywania w różnych wątkach natywnych, a zatem będą uruchamiane równolegle (przynajmniej do możliwości twojego komputera: jeśli twój system oparty jest na procesorze wielordzeniowym, jest prawdopodobne, że te wątki będą prawdziwie równoległe, jeśli twój procesor ma jeden rdzeń, wtedy wielozadaniowość wyprzedzająca zaimplementowana w wątkach systemu operacyjnego utworzy widoczność równoległego wykonania).

Możliwe jest ustawienie zmiennej GOMAXPROCS przy użyciu funkcji runtime.GOMAXPROCS() zamiast wstępnego ustawiania zmiennej środowiskowej. Użyj coś takiego w swoim programie, zamiast obecnego main:

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

W tym przypadku można zaobserwować ciekawe wyniki. Możliwe, że linie "cześć" i "świat" zostaną wydrukowane nierównomiernie, np.

hello 
hello 
world 
hello 
world 
world 
... 

Może się to zdarzyć, jeśli goroutines mają zaplanować oddzielenie wątków systemu operacyjnego. Tak naprawdę działa wielozadaniowość zapobiegawcza (lub przetwarzanie równoległe w przypadku systemów wielordzeniowych): wątki są równoległe, a ich połączony wynik jest indeterministyczny. BTW, możesz zostawić lub usunąć wywołanie Gosched, wydaje się, że nie ma to żadnego wpływu, gdy GOMAXPROCS jest większe niż 1.

Oto co otrzymałem na kilku przebiegach programu z numerem runtime.GOMAXPROCS.

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 

See, czasami wyjście jest ładna, czasami nie. Indeterminizm w akcji :)

Kolejna aktualizacja

Wygląda na to, że w nowszych wersjach Go Go kompilator sił wykonawczych goroutines uzyskując nie tylko na współbieżności prymitywów użytkowania, ale na OS system wywołuje też. Oznacza to, że kontekst wykonania można przełączać między goroutinami również w wywołaniach funkcji IO. W rezultacie w najnowszych kompilatorach Go można zaobserwować zachowanie indeterministyczne nawet wtedy, gdy GOMAXPROCS jest rozbrojone lub ustawione na 1.

+0

Świetna robota! Ale nie spotkałem się z tym problemem w wersji 1.0.3, dziwne. – MrROY

+1

To prawda. Właśnie sprawdziłem to z wersją 1.0.3, i tak, to zachowanie się nie pojawiło: nawet z GOMAXPROCS == 1 program działał tak, jakby GOMAXPROCS> = 2. Wydaje się, że w wersji 1.0.3 programowanie zostało zmodyfikowane. –

+0

dzięki! Chciałbym dwa razy to zrobić! –

3

Planowane jest wspólne planowanie. Bez ustępowania, drugi (powiedzmy "świat") goroutine może legalnie uzyskać zerową szansę na wykonanie przed/kiedy kończy się magistrala, co na specyfikację kończy wszystkie gorutyny - tj. cały proces.

+1

ok, więc wydajność "runtime.Gosched()". Co to znaczy? Daje kontrolę z powrotem do głównej funkcji? –

+4

W tym konkretnym przypadku tak. Zasadniczo prosi planistę, aby uruchomił i uruchomił jeden z "gotowych" goroutinów w celowo nieokreślonej kolejności selekcji. – zzzz

Powiązane problemy