Introduction
Les vérifications de Garden Night classent désormais correctement les réponses, mais le premier brouillon traite les tâches une par une. Une entrée lente retarde l’arrivée d’autres résultats utiles. Vous répartirez les tâches entre un petit nombre fixe de workers Go, tout en renvoyant chaque résultat demandé.
Cette unité indépendante contient déjà une fonction complète pour une cible. Vous devez connaître les fonctions, structures, slices, modules, la syntaxe élémentaire des canaux et les résultats HTTP de l’unité précédente, ou posséder une expérience équivalente. Les goroutines, la fermeture des canaux et la fin des workers sont expliquées avant leur utilisation. Seules des cibles locales préparées sont interrogées ; cet outil ponctuel n’est ni un banc de mesure ni une plateforme de supervision. Prévoyez environ 60 minutes, lecture et erreurs courantes comprises ; c’est une estimation de l’auteur.
Observer le travail réel avant de le répartir
Dans cette étape, lancez le vérificateur séquentiel actuel et examinez les observations réelles du service. Commencez dans Workspace. cd sélectionne le projet ; export ajoute le répertoire Go préparé au chemin de recherche des commandes de ce shell. Répétez ces lignes dans une nouvelle session.
cd /home/labex/project/site-checker
export PATH=/usr/local/go/bin:$PATH
Lisez la fonction de vérification, la boucle de travail et la liste sans expiration. cat affiche simplement les fichiers :
cat check.go run.go overlap.json
Check renvoie déjà ok, http_error, content_mismatch, connection_error ou timeout, conserve l’identité et le statut reçu, et ferme chaque corps. Le Run séquentiel ignore son paramètre workers. C’est le comportement que vous allez modifier.
Les entrées de overlap.json effectuent chacune un travail fini de 250ms. Choisissez un délai de deux secondes par cible pour étudier la concurrence, pas les expirations du client. go run . exécute ce paquet ; -input sélectionne les tâches, -workers demande un maximum de vérifications actives et -timeout 2s fixe le budget de requête. > écrit la sortie standard dans un fichier.
go run . -input overlap.json -workers 2 -timeout 2s > normal-report.json
Lisez-le :
cat normal-report.json
Les six résultats doivent être ok. Ce rapport seul ne montre pas s’ils se sont chevauchés. L’observateur préparé consigne des événements enter et exit lorsqu’un gestionnaire réel du serveur commence et termine ; active compte les gestionnaires traitant effectivement une requête. Le petit lecteur Python consulte ces observations existantes sans ajouter de requête cible :
python3 observe.py
Dans cette scène séquentielle neuve, attendez Actual handler peak: 1 ; les entrées et sorties alternent, avec au plus un gestionnaire actif. L’observateur était prêt avant ces requêtes. Le travail fini de 250ms offre aux clients concurrents une occasion de se chevaucher ; ni un retard ni la durée totale ne prouvent à eux seuls ce chevauchement. Les événements en sont la preuve.
Le brouillon produit un rapport complet, mais son travail actif maximal reste limité à un. Conservez ce rapport comme exemple normal de cette étape. La suivante ne remplace que run.go ; le service, les définitions de cibles et la fonction de vérification restent disponibles.
Confier les tâches à un nombre fixe de workers
Dans cette étape, concevez le passage des tâches et des résultats, puis modifiez Run dans Workspace. Gardez Check terminé et les entrées préparées inchangés. Votre résultat est un groupe de workers fonctionnel, pas simplement plusieurs goroutines démarrées.
Décider qui est responsable de chaque canal
Une goroutine est une fonction exécutée concurremment avec son appelant ; go func() { ... }() démarre immédiatement une fonction anonyme. Son démarrage ne prouve pas qu’une requête HTTP est active. Un canal transmet des valeurs typées : make(chan Target) crée un canal de tâches et make(chan Result) un canal de résultats. Sur ces canaux non tamponnés, jobs <- target envoie une valeur et attend un récepteur. for target := range jobs reçoit jusqu’à la fermeture et au vidage du canal. close(jobs) signifie qu’aucune autre valeur ne sera envoyée ; les valeurs déjà reçues restent valides.
Avant de modifier le code, décidez quelle partie du programme enverra les tâches, recevra les tâches, enverra les résultats, recevra les résultats et fermera chaque canal. Il doit y avoir un producteur de tâches et un collecteur de résultats. Les workers reçoivent les tâches et envoient les résultats. Seul le producteur sait qu’aucune autre tâche ne sera envoyée : il ferme donc jobs. Un worker ne peut pas fermer results sans risque lorsqu’un autre worker pourrait encore envoyer.
sync.WaitGroup suit la fin des workers. Une déclaration comme var finished sync.WaitGroup commence à zéro. Appelez finished.Add(1) avant de démarrer chaque worker ; dans ce worker, defer finished.Done() signale sa sortie. finished.Wait() attend que chaque worker enregistré ait appelé Done. Le canal de résultats ne peut fermer qu’après cette attente. Confiez cette attente et cette fermeture à une goroutine distincte : si la goroutine principale attendait avant de recevoir, les workers pourraient rester bloqués sur l’envoi de leurs résultats. Le producteur fonctionne aussi séparément pour que la goroutine principale reçoive les résultats pendant l’attribution des tâches.
Pour démarrer le nombre demandé de workers, une boucle de comptage comme for i := 0; i < workers; i++ { ... } initialise i à zéro, vérifie la limite supérieure avant chaque itération et incrémente i après elle. Son corps démarre un worker à chaque tour. Chaque worker doit appeler Check de manière synchrone, envoyer le résultat renvoyé, puis seulement recevoir une autre tâche. Cela borne les vérifications clientes actives au nombre de workers. Ne démarrez pas une nouvelle goroutine par cible dans le worker. Partagez un http.Client avec Timeout: budget ; ce client prend en charge les requêtes concurrentes. Seul le collecteur ajoute au slice du rapport : les workers ne modifient donc pas concurremment un slice ou une map partagés. Différents ordres de fin sont valides.
Pour une liste de cibles vide, le producteur ferme quand même jobs. Les workers terminent alors, la goroutine de fermeture ferme results et le collecteur peut renvoyer un slice vide initialisé. Suivez cette séquence avant de coder. Expliquez aussi pourquoi une cible avec trois workers doit se terminer et pourquoi plusieurs cibles avec un worker doivent être séquentielles.
Modifier une boucle de travail cohérente
Sauvegardez le fichier avec une extension autre que .go, pour que Go ne compile pas une deuxième définition de Run. cp copie le fichier sans modifier l’original :
cp run.go run.go.bak
Ouvrez le fichier existant :
nano run.go
Utilisez les flèches pour vous déplacer, tapez pour insérer du texte et utilisez Retour arrière ou Suppr pour retirer des caractères. Ctrl signifie maintenir Control en appuyant sur l’autre touche. Ctrl+W, func Run, Entrée recherche la fonction ; Ctrl+C annule une recherche. Ctrl+K coupe la ligne courante : utilisez-le uniquement si vous voulez la supprimer. Enregistrez avec Ctrl+O, confirmez run.go, puis Entrée ; Ctrl+X quitte. Pour abandonner des modifications involontaires, appuyez sur Ctrl+X, puis N lorsque nano demande d’enregistrer. À une invite de nom de fichier indésirable, Ctrl+C annule cette invite pour vous permettre de corriger la modification ou d’enregistrer sous le nom original.
Gardez la signature de fonction et le client HTTP existant. Ajoutez sync au groupe d’imports lorsque vous utilisez son WaitGroup. Remplacez la boucle séquentielle sur les cibles par votre conception, en traitant les responsabilités suivantes dans cet ordre :
D’abord, créez les canaux typés de tâches et de résultats ainsi qu’un WaitGroup de fin des workers. Enregistrez et démarrez exactement workers workers. Chaque worker reçoit jusqu’à la fermeture de jobs, envoie chaque résultat de Check et signale sa fin au retour.
Ensuite, démarrez un producteur distinct qui envoie chaque cible, y compris les URL répétées avec des identifiants différents. Fermez jobs après le dernier envoi, y compris s’il n’y avait aucune cible.
Démarrez aussi une goroutine de fin distincte qui attend tous les workers, puis ferme results. Elle ne doit pas fermer results tant qu’un worker peut encore envoyer.
Enfin, dans la goroutine principale, initialisez un slice de résultats vide, recevez chaque résultat jusqu’à la fermeture du canal, ajoutez chacun au slice et renvoyez-le.
Écrivez les quatre parties avant de lancer le programme. Un groupe incomplet sans émetteur, sans responsable de fermeture ou sans collecteur n’est pas une expérience intermédiaire utile. Vous décidez comment relier les parties ; aucun Run terminé n’est fourni à coller. Les noms de variables locales et les choix de synchronisation équivalents ne sont pas évalués.
Enregistrez et quittez, puis inspectez votre fichier réel. cat l’affiche sans le modifier :
cat run.go
gofmt -w met le code source en forme sur place. Une erreur de syntaxe indique le fichier et la ligne : rouvrez run.go, corrigez cette zone, enregistrez et répétez la mise en forme. N’écrasez pas vos parties terminées par une nouvelle réponse remplaçant tout le fichier.
gofmt -w run.go
Vérifier un véritable travail simultané
Lancez maintenant le groupe complet sur les tâches sans expiration avec deux workers. Le budget de deux secondes permet à leur travail fini de 250ms de se terminer normalement :
go run . -input overlap.json -workers 2 -timeout 2s
Chaque tâche doit renvoyer un résultat ok. Lisez les enregistrements existants de l’observateur :
python3 observe.py
Dans cette scène, le registre cumulatif doit maintenant afficher Actual handler peak: 2, avec un enter alors qu’un autre gestionnaire est encore actif. Si le maximum reste à un, inspectez comment les workers reçoivent et appellent Check ; la durée écoulée ne prouve pas une réussite. Un ordonnanceur inhabituellement lent peut rendre une observation non concluante. Lorsque le dernier événement est exit avec active=0, les requêtes finies sont réellement terminées ; vous pouvez répéter ce lot sans danger et lire ses nouveaux événements.
Un maximum cumulatif supérieur à deux signifie qu’un dépassement a eu lieu à un moment donné. Une implémentation incorrecte antérieure laisse ce maximum après correction du code source. Lisez ensemble les derniers événements d’entrée et de sortie, et utilisez Verify, qui exécute votre source originale dans un service de test indépendant, pour évaluer l’implémentation actuelle. N’effacez pas l’historique et ne réinitialisez pas la VM pour supprimer un ancien maximum.
Si la commande reste bloquée au lieu de revenir, Ctrl+C interrompt cette invocation au premier plan ; le service préparé séparément reste disponible. Rouvrez run.go et vérifiez la chaîne de responsabilité des canaux : toutes les tâches ont-elles été envoyées, le producteur a-t-il fermé jobs, les workers appellent-ils Done, le collecteur peut-il recevoir pendant leurs envois et la goroutine de fermeture finit-elle par fermer results ? Corrigez localement et mettez en forme. Avant une autre observation de concurrence, exécutez python3 observe.py et confirmez que le dernier événement enregistré est un exit avec active=0. Si des requêtes finies sont encore actives, relisez jusqu’à ce que cette condition enregistrée soit vraie ; n’utilisez pas d’attente arbitraire et ne supprimez pas les observations. Les expirations clientes n’arrêtent pas immédiatement le travail du serveur.
Si un fichier manque, utilisez pwd et le cd de la première étape pour retrouver le répertoire. Si go est indisponible dans un nouveau shell, répétez le export de session de cette étape. Ni une modification ordinaire ni une faute de chemin ne nécessitent de changer les fichiers globaux du shell ou de réinitialiser la VM.
Terminer toutes les tâches dans leur budget
Dans cette étape, utilisez le groupe de workers pour le rapport de l’événement et testez ses limites. Lisez les tâches réelles avant de les lancer :
cat targets.json
Les résultats attendus sont deux ok, un http_error avec le statut 403, un content_mismatch avec 200, un timeout avec zéro et un connection_error avec zéro. La billetterie impose un délai fini de 600ms ; le budget client de 200ms termine volontairement cette vérification plus tôt. Les deux identifiants de l’URL d’accueil répétée doivent rester dans le rapport.
Enregistrez un nouveau rapport avec deux workers. > remplace seulement report.json ; les erreurs restent visibles dans le terminal.
go run . -input targets.json -workers 2 -timeout 200ms > report.json
Lisez le fichier réellement enregistré :
cat report.json
L’ordre des lignes peut différer de l’entrée et varier entre exécutions. Associez-les par identifiant : une requête lente peut se terminer après une requête rapide attribuée plus tard. Un worker ne commence la cible suivante qu’au retour de Check, mais un gestionnaire dont le client a expiré peut poursuivre un travail fini après son départ. Le cours mesure donc la limite des workers sur des requêtes séparées sans expiration ; il ne confond pas ces gestionnaires persistants avec une erreur du groupe.
Le groupe doit aussi gérer moins de tâches que de workers, voire aucune tâche. Ces commandes utilisent les listes préparées et affichent directement leur résultat, sans modifier report.json :
go run . -input single.json -workers 3 -timeout 2s
go run . -input empty.json -workers 3 -timeout 2s
Attendez un résultat ok, puis [], avec deux commandes qui se terminent. Demandez enfin une limite d’un pour plusieurs cibles sans expiration :
go run . -input overlap.json -workers 1 -timeout 2s
Tous les résultats restent ok. Cette invocation doit être séquentielle même si votre code peut démarrer plusieurs workers. Le maximum cumulatif garde les activités précédentes et ne permet pas d’isoler cette invocation. La vérification du cours crée un service neuf et indépendant pour chaque budget, notamment un, deux et trois. Pour plusieurs cibles avec un budget de deux ou trois, elle exige un chevauchement réel d’au moins deux, sans dépasser la limite demandée ; elle n’impose pas un maximum exactement égal à trois. Aucune exigence de chevauchement ne s’applique aux lots vides ou à cible unique.
Conservez le rapport complet, les entrées et le service d’origine. Vous disposez d’une commande réutilisable associant travail actif limité et résultats complets, échecs fidèlement signalés compris.
Résumé
Vous avez utilisé des canaux de tâches et de résultats aux responsabilités de fermeture explicites, un nombre fixe de workers et une synchronisation de leur fin. Un seul collecteur conserve chaque résultat sans modifications concurrentes du slice partagé. Les événements réels du serveur distinguent exécution séquentielle, concurrence limitée et travail excessif ; les tests de délai séparés évitent de prendre un traitement serveur persistant pour un dépassement de workers. Le rapport reste utile même si certaines entrées échouent.



