Die folgende Kolumne stellt allen in der Kolumne Golang-Tutorial die Golang-Coroutine-Planung vor. Ich hoffe, dass sie für Freunde hilfreich sein wird, die sie brauchen!
1. Thread-Modell
- N:1-Modell, N User-Space-Threads laufen auf 1 Kernel-Space-Thread. Der Vorteil besteht darin, dass der Kontextwechsel sehr schnell erfolgt, die Vorteile von Multicore-Systemen jedoch nicht genutzt werden können.
- 1:1-Modell, 1 Kernel-Space-Thread führt 1 User-Space-Thread aus. Dadurch werden die Vorteile von Multicore-Systemen voll ausgenutzt, der Kontextwechsel ist jedoch sehr langsam, da jeder Zeitplan zwischen Benutzermodus und Kernelmodus wechselt. (POSIX-Thread-Modell (pthread), Java)
- M:N-Modell, jeder Benutzer-Thread entspricht mehreren Kernel-Space-Threads, und ein Kernel-Space-Thread kann auch mehreren User-Space-Threads entsprechen. Go beabsichtigt, dieses Modell zu übernehmen und eine beliebige Anzahl von Kernel-Modellen zu verwenden, um eine beliebige Anzahl von Goroutinen zu verwalten. Dies vereint die Vorteile der beiden oben genannten Modelle, der Nachteil ist jedoch die Komplexität der Planung.
Werfen wir einen Blick auf Golangs Coroutine-Planung
- M: ein User-Space-Thread, der einem Kernel-Thread entspricht, ähnlich wie Posix-Pthread
- P: stellt den laufenden Kontext dar, den Planer, den wir im vorherigen Abschnitt implementiert haben , ein Scheduler entspricht auch einer Bereitschaftswarteschlange
- G: Goroutine, also Coroutine
2. Einführung in das Planungsmodell
Groutine kann über das GPM-Planungsmodell eine leistungsstarke Parallelitätsimplementierung haben. Lassen Sie uns das Goroutine-Planungsmodell erklären .
Gos Scheduler enthält drei wichtige Strukturen: M, P, G
M: M ist eine Kapselung von Threads auf Kernelebene, und die Anzahl entspricht der tatsächlichen Anzahl von CPUs. Ein M ist ein Thread und eine Goroutine läuft auf Oben M; M ist eine große Struktur, die viele Informationen wie den kleinen Objektspeicher-Cache (mcache), die aktuell ausgeführte Goroutine, den Zufallszahlengenerator usw. verwaltet. G: stellt eine Goroutine dar, die über einen eigenen Stapel verfügt Zeiger und andere Informationen (wartende Kanäle usw.) für die Planung.
P: Der vollständige Name von P ist Prozessor. Sein Hauptzweck ist die Ausführung von Goroutine. Jedes Prozessorobjekt verfügt über eine LRQ (Local Run Queue). Nicht zugewiesene Goroutine-Objekte werden in der GRQ (Global Run Queue) gespeichert und warten darauf, einem bestimmten P in der LRQ zugewiesen zu werden.
Golang verwendet ein Multithreading-Modell, genauer gesagt ein zweistufiges Threading-Modell, das jedoch den System-Thread (Thread auf Kernel-Ebene) kapselt und eine leichtgewichtige Coroutine (Thread auf Benutzerebene) verfügbar macht Benutzer können es verwenden, und die Planung von Threads auf Benutzerebene zu Threads auf Kernelebene wird von Golangs Laufzeit übernommen, und die Planungslogik ist für die Außenwelt transparent. Der Vorteil von Goroutine besteht darin, dass der Kontextwechsel im vollständigen Benutzerstatus durchgeführt wird. Es ist nicht erforderlich, so häufig wie bei Threads zwischen Benutzerstatus und Kernelstatus zu wechseln, was den Ressourcenverbrauch spart.
Planungsimplementierung
Wie aus dem obigen Bild ersichtlich ist, gibt es zwei physische Threads M, jeder M hat einen Prozessor P und jeder hat auch eine laufende Goroutine.
Die Anzahl von P kann über GOMAXPROCS() festgelegt werden, was tatsächlich die tatsächliche Parallelität darstellt, dh wie viele Goroutinen gleichzeitig ausgeführt werden können.
Die grauen Goroutinen im Bild werden nicht ausgeführt, befinden sich jedoch im Bereitschaftszustand und warten auf die Planung. P verwaltet diese Warteschlange (Runqueue genannt). In der Go-Sprache ist es einfach, eine Goroutine zu starten: Gehen Sie einfach zur Go-Funktion, sodass bei jeder Ausführung einer Go-Anweisung eine Goroutine am Ende der Runqueue-Warteschlange hinzugefügt wird und die nächste Zeitplan ist Klicken Sie, nehmen Sie eine Goroutine aus der Runqueue (wie entscheidet man, welche Goroutine verwendet werden soll?) und führen Sie sie aus.
Wenn ein Betriebssystem-Thread M0 blockiert ist (wie unten gezeigt), führt P stattdessen M1 aus. M1 im Bild wird möglicherweise erstellt oder aus dem Thread-Cache entfernt.
Wenn das MO zurückkehrt, muss es versuchen, ein P zu erhalten, um die Goroutine auszuführen. Normalerweise erhält es ein P von anderen Betriebssystem-Threads.
Wenn es es nicht erhält, wird es den Goroutine-Put erhalten es in einem globalen
Runqueue und dann von selbst schlafen (in den Thread-Cache legen). Alle P werden außerdem in regelmäßigen Abständen eine globale Überprüfung durchführen
runqueue und führen Sie die darin enthaltene Goroutine aus, andernfalls wird die Goroutine in der globalen Runqueue niemals ausgeführt.
Eine andere Situation besteht darin, dass die von P zugewiesene Aufgabe G schnell abgeschlossen wird (ungleichmäßige Verteilung), was dazu führt, dass der Prozessor P sehr beschäftigt ist, andere P jedoch zu diesem Zeitpunkt noch Aufgaben haben, wenn sie global sind
Es gibt keine Aufgabe G in der Runqueue, daher muss P zur Ausführung etwas G von einem anderen P erhalten. Im Allgemeinen gilt: Wenn P eine Aufgabe von einem anderen P erhalten möchte, wird diese normalerweise ausgeführt
Die Hälfte der Warteschlange stellt sicher, dass jeder Betriebssystem-Thread vollständig genutzt werden kann, wie unten gezeigt:
3. Probleme im Zusammenhang mit der GPM-Erstellung
Wie ermittelt man die Anzahl von M und P? Oder wann entstehen M und P?
1. Die Anzahl von P:
- wird durch die Umgebungsvariable $GOMAXPROCS beim Start oder durch die Laufzeitmethode GOMAXPROCS() bestimmt (Standard ist 1). Dies bedeutet, dass zu jedem Zeitpunkt der Programmausführung nur $GOMAXPROCS-Goroutinen gleichzeitig ausgeführt werden.
2. Anzahl von M:
- Einschränkungen der Go-Sprache selbst: Wenn das Go-Programm startet, wird die maximale Anzahl von M festgelegt, mit einem Standardwert von 10000. Allerdings ist es für den Kernel schwierig, sie zu unterstützen so viele Threads, dass diese Grenze ignoriert werden kann.
- Die SetMaxThreads-Funktion in Runtime/Debug legt die maximale Anzahl von M fest.
- Wenn ein M blockiert ist, wird ein neues M erstellt.
M hat keine absolute Beziehung zur Anzahl von P. Wenn ein M blockiert ist, erstellt P ein anderes M oder wechselt zu einem anderen. Daher können viele M erstellt werden, selbst wenn die Standardanzahl von P 1 ist.
3. Wenn P erstellt wird: Nachdem die maximale Anzahl n von P ermittelt wurde, erstellt das Laufzeitsystem n Ps basierend auf dieser Anzahl.
4. Wenn M erstellt wird: Es ist nicht genug M vorhanden, um es mit P zu verknüpfen und das ausführbare G darin auszuführen. Wenn beispielsweise zu diesem Zeitpunkt alle M blockiert sind und in P noch viele Aufgaben bereit sind, wird nach einem freien M gesucht. Wenn kein freies M vorhanden ist, wird ein neues M erstellt.
Welchen P-Verband soll ich wählen?
- M wählt die P-Zuordnung aus, die zur Erstellung dieses M geführt hat.
Wann wird das Verhältnis zwischen P und M umgestellt?
Wenn M aufgrund eines Systemaufrufs blockiert ist (wenn G, das auf M ausgeführt wird, in einen Systemaufruf eintritt), werden M und P getrennt. Wenn sich zu diesem Zeitpunkt Aufgaben in der Bereitschaftswarteschlange von P befinden, ordnet
P ein inaktives M zu. oder erstellen Sie ein M für Assoziation. (Das heißt, Go verarbeitet E/A-Blockierungen nicht wie libtask? Ich bin mir nicht sicher.)
Wie wählt ein bereiter G aus, in welche P-Warteschlange er eintreten soll?
- Standardmäßig: Da die Standardanzahl von P 1 ist (M ist nicht unbedingt 1), werden sie, wenn wir GOMAXPROCS nicht ändern, unabhängig davon, wie viele Goroutinen wir mit Go-Anweisungen im Programm erstellen, nur in die gestopft gleiche A P's Bereitschaftswarteschlange.
- Wenn mehrere Ps vorhanden sind: Wenn GOMAXPROCS geändert oder runtime.GOMAXPROCS aufgerufen wird, verteilt das Laufzeitsystem alle Gs in der Bereitschaftswarteschlange jedes Ps gleichmäßig.
So stellen Sie sicher, dass die Bereitschaftswarteschlange jedes Ps über G verfügt.
Wenn alle Aufgaben in der Bereitschaftswarteschlange eines Ps ausgeführt wurden, versucht P, einen Teil der Bereitschaftswarteschlangen anderer Ps in seine eigene Bereitschaftswarteschlange zu übernehmen, um dies sicherzustellen dass die Bereitschaftswarteschlange jedes P Aufgaben hat, die ausgeführt werden können.
Das obige ist der detaillierte Inhalt vonInformationen zur Golang-Coroutine-Planung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!