2016-12-25 6 views
0

Heute erzählte mir ein Freund, dass Go-Programme auf mehreren CPU-Kernen skalieren können. Ich war ziemlich überrascht zu hören, dass das Wissen, dass System Task Scheduler nichts über Goroutines wissen und daher nicht auf mehreren Kernen laufen können.Golang, Prozesse und Shared Memory

Ich habe einige Suche und fand heraus, dass Go-Programme mehrere OS-Aufgaben erstellen können, um sie auf verschiedenen Kernen auszuführen (die Nummer wird von GOMAXPROCS Umgebungsvariable gesteuert). Aber soweit ich weiß, führt ein Forking zu einer vollständigen Kopie von Prozessdaten und verschiedenen Prozessen, die in verschiedenen Adressräumen ablaufen.

Was ist also mit globalen Variablen in Go-Programmen? Sind sie sicher mit mehreren Göroutinen zu verwenden? Synchronisieren sie irgendwie zwischen Systemprozessen? Und wenn sie es tun, wie? Ich bin hauptsächlich besorgt über Linux und Freebsd Implementierungen.

+2

[Die offizielle Dokumentation von Goroutines] (https://golang.org/doc/effective_go.html#goroutines) sollte hilfreich sein. – chrk

+0

Sowie [dieser Artikel] (https://www.goinggo.net/2014/01/concurrency-goroutines-and-gomaxprocs.html). – chrk

+0

@chrk, das Problem ist: nicht die offizielle Dokumentation noch der zweite Artikel beleuchten, wie gehen Laufzeit bietet die Synchronisation von gemeinsamen Variablen. Die offizielle Dokumentation besagt: "Eine Goroutine hat ein einfaches Modell: Es ist eine Funktion, die gleichzeitig mit anderen Goroutinen im selben Adressraum ausgeführt wird". Aber sie können nicht im gleichen Adressraum sein, wenn sie sich in verschiedenen Prozessen befinden ... – ea7ababe

Antwort

4

Ich habe es herausgefunden! Es ist alles in Go-Quellen.

Es gibt einen Linux-Systemaufruf, von dem ich nichts wusste. Es heißt "Klon". Es ist flexibler als fork und es ermöglicht einen untergeordneten Prozess, in dem Adressraum seiner Eltern zu leben.

Hier ist ein kurzer Überblick über den Thread-Erstellungsprozess.

Zuerst gibt es eine newm Funktion in src/runtime/proc.go. Diese Funktion ist verantwortlich für die Erstellung eines neuen Arbeitsthreads (oder Maschine, wie es in Kommentaren genannt wird).

// Create a new m. It will start off with a call to fn, or else the scheduler. 
// fn needs to be static and not a heap allocated closure. 
// May run with m.p==nil, so write barriers are not allowed. 
//go:nowritebarrier 
func newm(fn func(), _p_ *p) { 

    // ... some code skipped ... 

    newosproc(mp, unsafe.Pointer(mp.g0.stack.hi)) 
} 

Diese Funktion ruft newosproc die OS-spezifisch ist. Für Linux kann es in src/runtime/os_linux.go gefunden werden. Hier sind relevante Teile der Datei:

var (
    // ... 

    cloneFlags = _CLONE_VM | /* share memory */ 
     _CLONE_FS | /* share cwd, etc */ 
     _CLONE_FILES | /* share fd table */ 
     _CLONE_SIGHAND | /* share sig handler table */ 
     _CLONE_THREAD /* revisit - okay for now */ 
) 

// May run with m.p==nil, so write barriers are not allowed. 
//go:nowritebarrier 
func newosproc(mp *m, stk unsafe.Pointer) { 

    // ... some code skipped ... 

    ret := clone(cloneFlags, /* ... other flags ... */) 

    // ... code skipped 
} 

Und die clone Funktion wird in der Architektur spezifische Dateien definiert. Für amd64 ist es in src/runtime/sys_linux_amd64.s. Es ist der tatsächliche Systemaufruf.

Also Go-Programme laufen in mehreren OS-Threads, die über mehrere CPUs ermöglicht, aber sie verwenden einen gemeinsamen Adressraum.

Puh ... ich liebe gehen.