Channels et Communication
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 60 sur 107.
Les canaux (channels) sont le mécanisme principal de Go pour la communication entre les goroutines. Plutôt que de partager directement la mémoire, les goroutines envoient et reçoivent des valeurs via des canaux, en suivant la philosophie de Go : « Ne communiquez pas en partageant la mémoire ; partagez la mémoire en communiquant. »
Créez un canal en utilisant la fonction make avec le mot-clé chan :
messages := make(chan string)Utilisez l'opérateur flèche <- pour envoyer et recevoir des valeurs. La flèche pointe dans la direction du flux de données :
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // envoyer au canal
}()
msg := <-messages // recevoir du canal
fmt.Println(msg) // Output: hello
}Par défaut, les envois et les réceptions sont bloquants jusqu'à ce que l'autre côté soit prêt. Quand la goroutine envoie "hello", elle attend que main soit prêt à recevoir. Ce comportement bloquant fournit une synchronisation intégrée — pas besoin de time.Sleep.
Vous pouvez fermer un canal pour signaler qu'aucune autre valeur ne sera envoyée. Les récepteurs peuvent le détecter :
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Les channels sont typés — un chan string ne peut transporter que des chaînes de caractères. Cette sécurité de type garantit que les goroutines communiquent avec les bons types de données, permettant de détecter les erreurs à la compilation plutôt qu'à l'exécution.
Défi
FacileConstruisons un système de relais de messages qui démontre comment les goroutines communiquent via des canaux. Vous allez créer un pipeline où les messages circulent entre différentes étapes de traitement, chaque étape s'exécutant de manière concurrente.
Vous organiserez votre code sur deux fichiers :
relay.go: Définissez votre pipeline de traitement de messages.Créez une structure
Messageavec les champsID(int) etContent(string).Implémentez trois fonctions qui représentent les différentes étapes de votre pipeline :
Producer(messages []Message, out chan Message)- Prend une tranche (slice) de messages et envoie chacun d'eux vers le canal de sortie. Après avoir envoyé tous les messages, fermez le canal pour signaler la fin.Transformer(in chan Message, out chan Message)- Reçoit des messages du canal d'entrée, transforme le contenu de chaque message en majuscules et envoie le message transformé vers le canal de sortie. Lorsque le canal d'entrée est fermé (détecté à l'aide de la forme de réception à deux valeurs), fermez le canal de sortie.Consumer(in chan Message) []string- Reçoit tous les messages du canal d'entrée et les collecte dans une tranche de chaînes de caractères formatées. Chaque chaîne doit suivre le format :"Message %d: %s"(utilisantMessage [ID]: [Content]). Retournez la tranche lorsque le canal est fermé.
main.go: Configurez le pipeline et orchestrez le flux de messages concurrent.Lisez le nombre de messages, puis lisez l'ID et le contenu de chaque message. Créez les messages et configurez deux canaux pour connecter vos trois étapes de pipeline. Lancez le Producer et le Transformer en tant que goroutines, puis exécutez le Consumer dans la goroutine principale pour collecter les résultats. Affichez chaque résultat sur une ligne distincte.
Les entrées suivantes seront fournies :
- Ligne 1 : Nombre de messages (entier)
- Lignes suivantes : Pour chaque message, deux lignes - l'ID du message (entier), puis son contenu (chaîne de caractères)
Par exemple, avec :
2
1
hello world
2
go channelsVotre sortie devrait être :
Message 1: HELLO WORLD
Message 2: GO CHANNELSLe pipeline fonctionne ainsi : Producer envoie les messages → Transformer convertit en majuscules → Consumer collecte et formate les résultats. Chaque flèche représente un canal connectant des étapes concurrentes. Utilisez le package strings pour la transformation en majuscules.
Aide-mémoire
Les canaux (channels) sont le mécanisme principal de Go pour la communication entre les goroutines. Créez un canal en utilisant make avec le mot-clé chan :
messages := make(chan string)Utilisez l'opérateur flèche <- pour envoyer et recevoir des valeurs. La flèche pointe dans la direction du flux de données :
messages <- "hello" // envoyer au canal
msg := <-messages // recevoir du canalPar défaut, les envois et les réceptions sont bloquants jusqu'à ce que l'autre côté soit prêt, offrant ainsi une synchronisation intégrée :
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // envoyer au canal
}()
msg := <-messages // recevoir du canal
fmt.Println(msg) // Output: hello
}Fermez un canal pour signaler qu'aucune autre valeur ne sera envoyée. Les récepteurs peuvent détecter la fermeture en utilisant la forme de réception à deux valeurs :
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Les canaux sont typés — un chan string ne peut transporter que des chaînes de caractères, offrant ainsi une sécurité de type à la compilation.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le nombre de messages
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Lire les messages
messages := make([]Message, n)
for i := 0; i < n; i++ {
idLine, _ := reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(idLine))
contentLine, _ := reader.ReadString('\n')
content := strings.TrimSpace(contentLine)
messages[i] = Message{ID: id, Content: content}
}
// TODO: Créer deux canaux pour connecter les étapes du pipeline
// TODO: Lancer Producer en tant que goroutine
// TODO: Lancer Transformer en tant que goroutine
// TODO: Exécuter Consumer dans la goroutine principale et collecter les résultats
// TODO: Imprimer chaque résultat sur une ligne séparée
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool