Arbeit mit einem kleinen Worker-Pool verteilen

GolangBeginner
Jetzt üben

Einführung

Die Zugangsprüfungen für Garden Night klassifizieren Antworten nun korrekt, doch der erste Entwurf verarbeitet Aufträge nacheinander. Ein langsamer Zugang verzögert andere nützliche Ergebnisse. Du verteilst die Arbeit auf eine kleine feste Zahl von Go-Workern und lieferst weiterhin jedes zugewiesene Ergebnis zurück.

Diese eigenständige Einheit enthält bereits eine vollständige Prüffunktion für ein Ziel. Vorausgesetzt werden Go-Funktionen, Structs, Slices, Module, grundlegende Kanalsyntax und die HTTP-Ergebnisse der vorherigen Einheit oder entsprechende Erfahrung. Goroutinen, das Schließen von Kanälen und die Worker-Beendigung werden vor ihrer Verwendung erklärt. Angefragt werden nur vorbereitete lokale Ziele; dies ist ein einmaliges Werkzeug, keine Benchmark- oder Überwachungsplattform. Plane einschließlich Lesen und üblicher Fehler etwa 60 Minuten ein; das ist eine Schätzung des Autors.

Echte Arbeit vor dem Verteilen beobachten

In diesem Schritt startest du den aktuellen sequenziellen Prüfer und liest tatsächliche Dienstbeobachtungen. Beginne in Workspace. cd wählt das Projekt; export ergänzt den vorbereiteten Go-Pfad in der Befehlssuche dieser Shell. Wiederhole beide Zeilen in einer neuen Terminalsitzung.

cd /home/labex/project/site-checker
export PATH=/usr/local/go/bin:$PATH

Lies Prüffunktion, Arbeitsschleife und die Aufträge ohne Timeout. cat zeigt die Dateien nur an:

cat check.go run.go overlap.json

Check liefert bereits ok, http_error, content_mismatch, connection_error oder timeout, behält Identität und empfangenen Status und schließt jeden Körper. Das sequenzielle Run ignoriert seinen Parameter workers. Dieses Verhalten wirst du ändern.

Jeder Zugang in overlap.json führt endliche 250ms vorbereiteter Arbeit aus. Nutze zwei Sekunden Timeout pro Ziel, damit diese Beobachtung Nebenläufigkeit statt Clienttimeouts untersucht. go run . startet dieses Paket; -input wählt die Aufträge, -workers fordert eine maximale Zahl aktiver Prüfungen an, und -timeout 2s setzt das Anfragebudget. > schreibt die Standardausgabe in eine Datei.

go run . -input overlap.json -workers 2 -timeout 2s > normal-report.json

Lies sie:

cat normal-report.json

Alle sechs Ergebnisse sollten ok sein. Der Bericht allein zeigt nicht, ob sie sich überlappten. Der vorbereitete Beobachter erfasst enter- und exit-Ereignisse, wenn ein echter Serverhandler beginnt und endet; active zählt tatsächlich an Anfragen arbeitende Handler. Das kleine Python-Leseprogramm ruft nur vorhandene Beobachtungen ab und erzeugt keine zusätzlichen Zielanfragen:

python3 observe.py

Erwarte in dieser frischen sequenziellen Umgebung Actual handler peak: 1; Beginn und Ende wechseln sich ab, höchstens ein Handler ist aktiv. Der Beobachter war vor den Anfragen bereit. Die endlichen 250ms Arbeit geben nebenläufigen Clients Gelegenheit zur Überlappung; eine Verzögerung oder Gesamtlaufzeit beweist sie nicht allein. Die Ereignisse sind der Beleg.

Der Entwurf erzeugt bereits einen vollständigen Bericht, aber höchstens eine Arbeit ist gleichzeitig aktiv. Behalte diesen Bericht als normales Beispiel für diesen Schritt. Im nächsten ersetzt du nur run.go; Veranstaltungsdienst, Zieldefinitionen und Prüffunktion bleiben verfügbar.

Aufträge einer festen Zahl von Workern geben

In diesem Schritt entwirfst du den Weg von Aufträgen und Ergebnissen und bearbeitest dann Run in Workspace. Erhalte das vollständige Check und die vorbereiteten Eingaben unverändert. Dein Ergebnis ist ein funktionierender Pool, nicht bloß mehrere gestartete Goroutinen.

Die Verantwortung für jeden Kanal festlegen

Eine Goroutine ist eine Funktion, die nebenläufig zu ihrem Aufrufer läuft; go func() { ... }() startet sofort eine anonyme Funktion. Ihr Start beweist keine aktive HTTP-Anfrage. Ein Kanal überträgt typisierte Werte: make(chan Target) erzeugt einen Auftragskanal und make(chan Result) einen Ergebniskanal. Bei diesen ungepufferten Kanälen sendet jobs <- target einen Wert und wartet auf einen Empfänger. for target := range jobs empfängt, bis der Kanal geschlossen und geleert ist. close(jobs) bedeutet, dass keine weiteren Werte gesendet werden; bereits empfangene Werte bleiben gültig.

Entscheide vor dem Bearbeiten, welcher Programmteil Aufträge sendet, Aufträge empfängt, Ergebnisse sendet, Ergebnisse empfängt und jeden Kanal schließt. Es sollte einen Auftragsproduzenten und einen Ergebnissammler geben. Worker empfangen Aufträge und senden Ergebnisse. Nur der Produzent weiß, wann keine Aufträge mehr gesendet werden; daher schließt er jobs. Ein Worker kann results nicht sicher schließen, solange ein anderer Worker noch senden könnte.

sync.WaitGroup verfolgt die Fertigstellung. Eine Deklaration wie var finished sync.WaitGroup beginnt bei null. Rufe finished.Add(1) vor dem Start jedes Workers auf; innerhalb des Workers markiert defer finished.Done() dessen Ende. finished.Wait() wartet, bis jeder angemeldete Worker Done aufgerufen hat. Der Ergebniskanal darf erst danach schließen. Gib diese Warte- und Schließverantwortung einer getrennten Goroutine: Würde die Hauptgoroutine vor dem Empfangen warten, könnten Worker beim Senden ihrer Ergebnisse blockiert bleiben. Auch der Produzent läuft getrennt, damit die Hauptgoroutine während der Arbeitszuweisung Ergebnisse empfangen kann.

Um die angeforderte Zahl von Workern zu starten, initialisiert eine Zählschleife wie for i := 0; i < workers; i++ { ... } den Wert i mit null, prüft vor jedem Durchlauf die Obergrenze und erhöht i danach. Ihr Rumpf startet jedes Mal einen Worker. Jeder Worker muss Check synchron aufrufen, das zurückgegebene Ergebnis senden und erst dann den nächsten Auftrag empfangen. Dies begrenzt aktive Clientprüfungen auf die Worker-Zahl. Starte innerhalb eines Workers keine neue Goroutine pro Ziel. Teile einen http.Client mit Timeout: budget; dieser Client unterstützt nebenläufige Anfragen. Nur der Sammler hängt an das Berichts-Slice an; Worker konkurrieren daher nicht um ein gemeinsames Slice oder eine Map. Unterschiedliche Abschlussreihenfolgen sind gültig.

Bei einer leeren Zielliste schließt der Produzent jobs trotzdem. Die Worker enden, der Schließer schließt results und der Sammler kann ein initialisiertes leeres Slice zurückgeben. Verfolge diese Folge vor dem Programmieren. Erkläre auch, warum ein Ziel mit drei Workern enden muss und warum mehrere Ziele mit einem Worker sequenziell sein müssen.

Eine zusammenhängende Arbeitsschleife bearbeiten

Sichere die Datei mit einer anderen Endung als .go, damit Go keine zweite Definition von Run kompiliert. cp kopiert die Datei, ohne das Original zu verändern:

cp run.go run.go.bak

Öffne die vorhandene Datei:

nano run.go

Bewege dich mit den Pfeiltasten, füge Text durch Tippen ein und entferne Zeichen mit Backspace oder Delete. Ctrl bedeutet, Control beim Drücken der anderen Taste zu halten. Ctrl+W, func Run, Enter sucht die Funktion; Ctrl+C bricht eine Suche ab. Ctrl+K schneidet die aktuelle Zeile aus; verwende es nur, wenn du diese Zeile entfernen möchtest. Speichere mit Ctrl+O, bestätige run.go und dann Enter; Ctrl+X beendet. Um unbeabsichtigte Änderungen zu verwerfen, drücke Ctrl+X und dann N, wenn nano nach dem Speichern fragt. Bei einer unerwünschten Dateinamensabfrage bricht Ctrl+C diese Abfrage ab, sodass du die Änderung korrigieren oder unter dem ursprünglichen Dateinamen speichern kannst.

Erhalte die Funktionssignatur und den vorhandenen HTTP-Client. Ergänze sync in der Importgruppe, wenn du dessen WaitGroup verwendest. Ersetze die sequenzielle Zielschleife durch deinen Entwurf und bearbeite diese Verantwortlichkeiten der Reihe nach:

Erstelle zuerst typisierte Auftrags- und Ergebniskanäle sowie einen WaitGroup für die Worker-Fertigstellung. Melde genau workers Worker an und starte sie. Jeder Worker empfängt bis zum Schließen von jobs, sendet jedes Ergebnis von Check und markiert bei der Rückkehr seine Fertigstellung.

Starte danach einen getrennten Produzenten, der jedes Ziel sendet, einschließlich wiederholter URLs mit unterschiedlichen IDs. Schließe jobs nach dem letzten Senden, auch wenn es keine Ziele gab.

Starte außerdem eine getrennte Abschlussgoroutine, die auf alle Worker wartet und dann results schließt. Sie darf results nicht schließen, solange noch ein Worker senden kann.

Initialisiere schließlich in der Hauptgoroutine ein leeres Ergebnis-Slice, empfange jedes Ergebnis bis zum Schließen des Ergebniskanals, hänge jedes an und gib das Slice zurück.

Schreibe alle vier Teile vor der Programmausführung. Ein unvollständiger Pool ohne Sender, Schließer oder Sammler ist kein nützliches Zwischenexperiment. Du entscheidest, wie die Teile verbunden werden; es gibt kein bereitgestelltes fertiges Run zum Einfügen. Lokale Variablennamen oder gleichwertige Synchronisationsentscheidungen werden nicht bewertet.

Speichere und beende; prüfe dann deine tatsächliche Datei. cat zeigt sie unverändert an:

cat run.go

gofmt -w formatiert Quelltext direkt. Ein Syntaxfehler nennt Datei und Zeile: Öffne run.go erneut, repariere diesen Bereich, speichere und wiederhole die Formatierung. Überschreibe deine fertigen Teile nicht mit einer neuen vollständigen Dateilösung.

gofmt -w run.go

Echte überlappende Arbeit prüfen

Führe nun den vollständigen Pool mit zwei Workern für die Aufträge ohne Timeout aus. Das Zwei-Sekunden-Budget lässt deren endliche 250ms Arbeit normal abschließen:

go run . -input overlap.json -workers 2 -timeout 2s

Jeder Auftrag sollte ein ok-Ergebnis zurückgeben. Lies die vorhandenen Aufzeichnungen des Beobachters:

python3 observe.py

In dieser Umgebung sollte die kumulierte Ereignisfolge nun Actual handler peak: 2 zeigen, mit einem enter, während ein anderer Handler noch aktiv ist. Bleibt der Höchstwert eins, prüfe, wie Worker empfangen und Check aufrufen; die verstrichene Zeit beweist keinen Erfolg. Ein ungewöhnlich verzögerter Scheduler kann eine Beobachtung uneindeutig machen. Sobald die neueste Ereignisfolge mit exit und active=0 endet, sind die endlichen Anfragen tatsächlich abgeschlossen; du darfst den ungefährlichen Auftragssatz wiederholen und seine neuen Ereignisse lesen.

Ein kumulierter Höchstwert über zwei bedeutet, dass irgendwann Arbeit oberhalb der Grenze stattfand. Eine frühere falsche Implementierung hinterlässt diesen Höchstwert nach der Quelltextkorrektur. Lies die neuesten Eintritts-/Austrittsereignisse zusammen und verwende Verify, das deine ursprünglichen Quellen mit einem unabhängigen Testdienst ausführt, um die aktuelle Implementierung zu beurteilen. Lösche keine Historie und setze die VM nicht zurück, um einen früheren Höchstwert zu beseitigen.

Bleibt der Befehl hängen, statt zurückzukehren, unterbricht Ctrl+C diesen Vordergrundaufruf; der getrennt vorbereitete Veranstaltungsdienst bleibt verfügbar. Öffne run.go erneut und prüfe die Kanalkette: Wurden alle Aufträge gesendet, schloss der Produzent jobs, rufen Worker Done auf, kann der Sammler während ihres Sendens empfangen und schließt der Schließer schließlich results? Repariere lokal und formatiere. Führe vor einer weiteren Nebenläufigkeitsbeobachtung python3 observe.py aus und bestätige, dass sein letztes aufgezeichnetes Ereignis ein exit mit active=0 ist. Sind endliche Anfragen noch aktiv, lies erneut, bis diese aufgezeichnete Bedingung wahr ist; verwende keine geschätzte Wartezeit und lösche keine Beobachtungen. Clienttimeouts stoppen Serverarbeit nicht sofort.

Fehlt eine Datei, finde das Verzeichnis mit pwd und dem cd des ersten Schritts wieder. Ist go in einer neuen Shell nicht verfügbar, wiederhole das sitzungsbezogene export aus jenem Schritt. Weder gewöhnliche Bearbeitungs- noch Pfadfehler erfordern eine Änderung globaler Shell-Dateien oder einen VM-Reset.

Jeden Auftrag innerhalb seines Budgets abschließen

In diesem Schritt nutzt du den Worker-Pool für den Veranstaltungsbericht und prüfst seine Grenzen. Lies vor dem Start die tatsächlichen Aufträge:

cat targets.json

Erwartet werden zwei ok, ein http_error mit Status 403, ein content_mismatch mit 200, ein timeout mit 0 und ein connection_error mit 0. Der Ticketdienst verzögert seine Antwort um endliche 600ms; das Clientbudget von 200ms beendet diese Prüfung bewusst früher. Beide IDs der wiederholten Anmelde-URL müssen im Bericht erhalten bleiben.

Speichere einen neuen Bericht mit zwei Workern. > ersetzt nur report.json; Fehler bleiben im Terminal sichtbar.

go run . -input targets.json -workers 2 -timeout 200ms > report.json

Lies deine tatsächlich gespeicherte Datei:

cat report.json

Die Reihenfolge kann von der Eingabeliste und zwischen Durchläufen abweichen. Ordne anhand der Auftrags-ID zu: Eine langsame Anfrage kann nach einer später zugewiesenen schnellen enden. Ein einzelner Worker beginnt sein nächstes Ziel erst, wenn Check zurückkehrt. Ein Serverhandler kann aber nach dem Timeout seines Clients weiterhin endliche Arbeit ausführen. Deshalb misst der Kurs die Worker-Obergrenze mit getrennten Anfragen ohne Timeout; nachlaufende Serverhandler werden nicht als Poolfehler gewertet.

Der Pool muss auch weniger Aufträge als Worker oder gar keine Aufträge verarbeiten. Diese Befehle nutzen vorbereitete Listen und zeigen Ergebnisse direkt an, ohne report.json zu ändern:

go run . -input single.json -workers 3 -timeout 2s
go run . -input empty.json -workers 3 -timeout 2s

Erwarte ein ok und danach []; beide Befehle müssen enden. Fordere abschließend eine Obergrenze von eins für mehrere Ziele ohne Timeout an:

go run . -input overlap.json -workers 1 -timeout 2s

Alle Ergebnisse bleiben ok. Dieser Aufruf muss sequenziell arbeiten, auch wenn dein Code mehrere Worker starten kann. Der kumulierte Höchstwert behält frühere Aktivität und kann die Grenze dieses Aufrufs nicht isolieren. Die Kursprüfung erstellt für jedes Budget einen getrennten frischen Testdienst, auch für eins, zwei und drei. Bei mehreren Zielen und Budget zwei oder drei verlangt sie eine echte Überlappung von mindestens zwei, ohne die angeforderte Obergrenze zu überschreiten; ein Höchstwert von genau drei ist nicht nötig. Für leere Auftragssätze und ein einzelnes Ziel ist keine Überlappung vorgeschrieben.

Behalte deinen vollständigen Veranstaltungsbericht sowie die ursprünglichen Eingaben und den Dienst. Dein wiederverwendbarer Befehl verbindet nun begrenzte aktive Arbeit mit vollständigen Ergebnissen, einschließlich ehrlich gemeldeter Fehlschläge.

Zusammenfassung

Du hast Auftrags- und Ergebniskanäle mit klarer Schließverantwortung, eine feste Worker-Zahl und Abschlusskoordination verwendet. Ein einzelner Sammler bewahrt alle Ergebnisse, ohne konkurrierende Änderungen am gemeinsamen Slice. Echte Serverereignisse unterscheiden sequenzielle, begrenzte nebenläufige und übermäßige Arbeit. Getrennte Timeouttests verhindern, dass nachlaufende Serverarbeit als Verletzung der Worker-Grenze gilt. Dein Bericht bleibt auch bei fehlgeschlagenen Zugängen nützlich.