Uwaga:
Począwszy od wersji Go 1.5, GOMAXPROCS jest ustawiony na liczbę rdzeni sprzętu: golang.org/doc/go1.5#runtime , poniżej oryginalnej odpowiedzi przed 1.5.
Gdy uruchamiasz program Go bez określenia zmiennej środowiskowej GOMAXPROCS, goroutiny Go są planowane do wykonania w pojedynczym wątku systemu operacyjnego. Jednak aby program wyglądał na wielowątkowość (do tego służą gorutyny, prawda?), Program planujący Go musi czasami zmieniać kontekst wykonania, aby każdy z nich mógł wykonać swoją pracę.
Jak powiedziałem, gdy zmienna GOMAXPROCS nie jest określona, środowisko uruchomieniowe Go może używać tylko jednego wątku, więc nie można przełączać kontekstów wykonywania, gdy goroutine wykonuje jakąś konwencjonalną pracę, taką jak obliczenia lub nawet IO (która jest odwzorowana na zwykłe funkcje C ). Kontekst można przełączyć tylko wtedy, gdy używane są prymitywy współbieżności Go, np. Gdy włączasz kilka kanałów lub (tak jest w twoim przypadku), gdy jawnie nakazujesz harmonogramowi przełączanie kontekstów - do tego służy runtime.Gosched
.
Więc, w skrócie, kiedy kontekst wykonania w jednej gorutynie osiągnie Gosched
wywołanie, program planujący jest instruowany, aby przełączyć wykonanie na inną gorutynę. W twoim przypadku są dwa gorutiny, główny (który reprezentuje „główny” wątek programu) i dodatkowy, ten, który utworzyłeś go say
. Jeśli usuniesz Gosched
wywołanie, kontekst wykonania nigdy nie zostanie przeniesiony z pierwszego gorutynu do drugiego, stąd nie będzie dla Ciebie „świata”. Kiedy Gosched
jest obecny, planista przenosi wykonanie w każdej iteracji pętli z pierwszej gorutyny do drugiej i odwrotnie, więc masz przeplatane „witaj” i „świat”.
FYI, nazywa się to „wielozadaniowością opartą na współpracy”: gorutyny muszą wyraźnie ustępować innym gorutynom. Podejście stosowane w większości współczesnych systemów operacyjnych nazywa się „wielozadaniowością wywłaszczającą”: wątki wykonawcze nie są związane z przenoszeniem sterowania; zamiast tego program planujący przełącza konteksty wykonywania na nie. Podejście kooperatywne jest często stosowane do implementacji „zielonych wątków”, to znaczy logicznych współbieżnych procedur, które nie mapują 1: 1 na wątki systemu operacyjnego - w ten sposób implementowane jest środowisko uruchomieniowe Go i jego gorutyny.
Aktualizacja
Wspomniałem o zmiennej środowiskowej GOMAXPROCS, ale nie wyjaśniłem, co to jest. Czas to naprawić.
Gdy ta zmienna jest ustawiona na liczbę dodatnią N
, środowisko uruchomieniowe Go będzie mogło utworzyć do N
wątków natywnych, dla których zaplanowane zostaną wszystkie zielone wątki. Wątek macierzysty rodzaj wątku, który jest tworzony przez system operacyjny (wątki Windows, pthreads itp.). Oznacza to, że jeśli N
jest większe niż 1, możliwe jest, że gorutyny zostaną zaplanowane do wykonania w różnych wątkach natywnych iw konsekwencji będą działać równolegle (przynajmniej do możliwości komputera: jeśli system jest oparty na procesorze wielordzeniowym, jest prawdopodobne, że te wątki będą naprawdę równoległe; jeśli twój procesor ma jeden rdzeń, wielozadaniowość z wywłaszczaniem zaimplementowana w wątkach systemu operacyjnego zapewni widoczność wykonywania równoległego).
Możliwe jest ustawienie zmiennej GOMAXPROCS za pomocą runtime.GOMAXPROCS()
funkcji zamiast wstępnego ustawiania zmiennej środowiskowej. Użyj czegoś takiego w swoim programie zamiast bieżącego main
:
func main() {
runtime.GOMAXPROCS(2)
go say("world")
say("hello")
}
W tym przypadku można zaobserwować ciekawe wyniki. Może się zdarzyć, że linie „hello” i „world” zostaną wydrukowane nierównomiernie, np
hello
hello
world
hello
world
world
...
Może się to zdarzyć, jeśli goroutyny mają oddzielać wątki systemu operacyjnego. W rzeczywistości tak działa wielozadaniowość z wywłaszczaniem (lub przetwarzanie równoległe w przypadku systemów wielordzeniowych): wątki są równoległe, a ich łączny wynik jest nieokreślony. BTW, możesz opuścić lub usunąć Gosched
połączenie, wydaje się, że nie ma to żadnego efektu, gdy GOMAXPROCS jest większe niż 1.
Oto, co otrzymałem w kilku uruchomieniach programu z runtime.GOMAXPROCS
wywołaniem.
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
Widzisz, czasami wyjście jest ładne, a czasami nie. Indeterminizm w akcji :)
Kolejna aktualizacja
Wygląda na to, że w nowszych wersjach kompilatora Go środowisko uruchomieniowe Go wymusza na gorutynach nie tylko wykorzystanie elementów podstawowych współbieżności, ale także wywołań systemowych systemu operacyjnego. Oznacza to, że kontekst wykonania może być przełączany między gorutynami również przy wywołaniach funkcji IO. W związku z tym w najnowszych kompilatorach Go można zaobserwować niezdeterministyczne zachowanie, nawet gdy GOMAXPROCS jest nieustawiony lub ustawiony na 1.
Gosched()
jest potrzebny , czy nie , zależy od twojego programu, nie zależy odGOMAXPROCS
wartości. Skuteczność wielozadaniowości z wywłaszczaniem w stosunku do kooperatywnej zależy również od programu. Jeśli Twój program jest powiązany z operacjami we / wy, wówczas współpraca wielozadaniowa z asynchronicznym we / wy będzie prawdopodobnie bardziej wydajna (tj. Będzie miała większą przepustowość) niż synchroniczne operacje we / wy oparte na wątkach; jeśli twój program jest związany z procesorem (np. długie obliczenia), wówczas wielozadaniowość kooperacyjna będzie znacznie mniej przydatna.Winowajcą jest wspólne planowanie. Bez ustępowania, druga gorutyna (powiedzmy „świat”) może legalnie mieć zerowe szanse na wykonanie przed / po zakończeniu main, co zgodnie ze specyfikacją kończy wszystkie gorutyny - tj. cały proces.
źródło
runtime.Gosched()
ustępuje. Co to znaczy? Czy daje kontrolę z powrotem do głównej funkcji?