Le mot-clé chan déclare un type de channel. Un channel est un tuyau typé par lequel les goroutines se transmettent des valeurs, et chaque envoi ou réception synchronise aussi les deux goroutines concernées. chan Job transporte des valeurs Job dans les deux sens, chan<- Job ne peut qu'envoyer et <-chan Job ne peut que recevoir. Un channel doit être créé avec make avant d'être utilisé, car la valeur zéro d'un type channel est nil (Go spec).
En bref
make(chan Job)crée un channel non bufferisé. Un envoi bloque jusqu'à ce qu'une autre goroutine reçoive la valeur, si bien que chaque envoi est une passation de main.make(chan Job, 100)crée un channel bufferisé. Les envois ne bloquent que lorsque le buffer est plein, et les réceptions que lorsqu'il est vide.chan<- Jobet<-chan Jobdans la signature d'une fonction la limitent à l'envoi ou à la réception. Le compilateur rejette tout le reste.- C'est l'émetteur qui ferme le channel, jamais le récepteur.
for job := range jobss'arrête après une fermeture, etv, ok := <-chrenvoieok == falseune fois le channel fermé et vide. - Envoyer sur un channel fermé provoque un panic, tout comme fermer deux fois un channel. Sur un channel
nil, toute opération saufclosebloque indéfiniment. fatal error: all goroutines are asleep - deadlock!signifie que toutes les goroutines sont bloquées. Une fuite en est la version plus discrète, où seules certaines goroutines restent coincées. Go 1.27 les détecte avec le profilgoroutineleak.selectattend sur plusieurs opérations de channel à la fois. Il vous donne les timeouts, l'annulation et les envois non bloquants.
Comment déclarer et créer un channel en Go ?
Écrivez chan suivi du type des éléments. Une variable déclarée ainsi vaut nil tant que vous ne lui affectez pas un channel créé par make :
example.gogopackage main import "fmt" type Job struct { ID int URL string } func main() { var queue chan Job fmt.Println(queue == nil, len(queue), cap(queue)) queue = make(chan Job, 100) queue <- Job{ID: 1, URL: "https://api.example.com/webhooks/stripe"} queue <- Job{ID: 2, URL: "https://api.example.com/webhooks/github"} fmt.Println(len(queue), cap(queue)) job := <-queue fmt.Println(job.ID, job.URL) }
example.texttexttrue 0 0 2 100 1 https://api.example.com/webhooks/stripe
ch <- v envoie et <-ch reçoit. La flèche indique toujours le sens dans lequel circulent les données. cap renvoie la taille de buffer passée à make, et len le nombre de valeurs présentes dans le buffer à cet instant. Les valeurs sortent dans l'ordre où elles sont entrées.
Un channel est une référence vers une structure du runtime, comme une map. Le passer à une fonction ou le stocker dans un struct copie la référence, donc toutes les copies communiquent avec le même channel. Le mot-clé var en Go et var ou make présentent les channels, les maps et les slices comme les trois types que l'on crée habituellement avec make.
Quelle différence entre channels bufferisés et non bufferisés ?
Un channel non bufferisé n'a aucun espace de stockage. Un envoi attend qu'un récepteur prenne la valeur, et une réception attend qu'un émetteur en propose une. Les deux côtés se rejoignent au même moment. La spécification Go le formule ainsi : « communication succeeds only when both a sender and receiver are ready ».
Ici, un handler d'upload transmet un nom de fichier à un antivirus qui met 100ms à démarrer :
example.gogofunc main() { uploads := make(chan string) go func() { time.Sleep(100 * time.Millisecond) fmt.Println("scanner: ready") for name := range uploads { _ = name // virus-scan the file } }() start := time.Now() uploads <- "invoice-1001.pdf" fmt.Println("handler: send returned after", time.Since(start).Round(10*time.Millisecond)) }
example.texttextscanner: ready handler: send returned after 100ms
L'envoi ne pouvait pas aboutir avant que l'antivirus soit prêt à recevoir. Quand l'envoi rend la main, le handler sait que l'antivirus a la valeur. Cette garantie est utile en soi, et le modèle mémoire de Go l'énonce formellement : une réception sur un channel non bufferisé est synchronisée avant la fin de l'envoi correspondant.
Un channel bufferisé stocke jusqu'à cap valeurs. Avec make(chan string, 10), l'envoi du handler aurait rendu la main immédiatement, et l'antivirus aurait récupéré le fichier 100ms plus tard. L'émetteur ne bloque que lorsque les 10 emplacements sont pleins.
Quelle taille de buffer donner à un channel Go ?
Commencez par zéro ou un, et ne choisissez une valeur plus grande que si vous savez dire à quoi elle sert. Un buffer ne rend pas un consommateur lent plus rapide. Si les producteurs vont durablement plus vite que les consommateurs, un buffer de 1 000 se remplit puis se comporte comme un channel non bufferisé, avec 1 000 valeurs de latence supplémentaire devant lui.
Les buffers sont utiles dans trois situations :
- Une goroutine qui produit un seul résultat.
make(chan Result, 1)lui permet d'envoyer puis de se terminer même si personne ne reçoit, et c'est justement la correction de la fuite présentée plus bas. - Les pics. Un exportateur de métriques qui reçoit 50 échantillons d'un coup et les envoie par lots une fois par seconde peut utiliser un buffer de la taille d'un pic normal.
- La limitation de la concurrence. Un channel bufferisé de capacité N sert de sémaphore qui laisse N goroutines s'exécuter en même temps. La section sur les patterns en donne un exemple.
Que signifient chan<- et <-chan en Go ?
Ce sont des types de channel directionnels. chan<- Job sert uniquement à l'envoi et <-chan Job uniquement à la réception. Un chan Job bidirectionnel se convertit automatiquement en l'un ou l'autre, si bien que vous créez le channel une seule fois et ne donnez à chaque fonction que le sens dont elle a besoin.
Un pipeline de webhooks avec un producteur et une étape de livraison ressemble à ceci :
example.gogotype Result struct { JobID int Status int } func produce(ids []int, out chan<- Job) { for _, id := range ids { out <- Job{ID: id, URL: fmt.Sprintf("https://hooks.example.com/%d", id)} } close(out) } func deliver(in <-chan Job, results chan<- Result) { for job := range in { results <- Result{JobID: job.ID, Status: 200} } close(results) } func main() { jobs := make(chan Job) results := make(chan Result) go produce([]int{101, 102, 103}, jobs) go deliver(jobs, results) for r := range results { fmt.Printf("job %d: %d\n", r.JobID, r.Status) } }
example.texttextjob 101: 200 job 102: 200 job 103: 200
Les signatures indiquent qui possède quelle extrémité. En lisant deliver, vous savez qu'elle n'écrit jamais dans in et ne le ferme jamais. Le compilateur y veille. Si deliver tente d'envoyer sur son entrée ou de la fermer :
example.gogopackage main type Job struct{ ID int } func deliver(in <-chan Job) { in <- Job{ID: 1} close(in) }
example.texttext./main.go:6:2: invalid operation: cannot send to receive-only channel <-chan Job in (variable of type <-chan Job) ./main.go:7:8: invalid operation: cannot close receive-only channel in (variable of type <-chan Job)
La fermeture compte comme une opération côté envoi, donc seul un chan<- ou un channel bidirectionnel peut être fermé. La règle de propriété habituelle, « c'est l'émetteur qui ferme », devient ainsi quelque chose que le système de types vérifie pour vous.
Comment fermer un channel en Go ?
Appelez close(ch). La fermeture indique aux récepteurs qu'aucune autre valeur n'arrivera. Elle ne jette pas les valeurs déjà présentes dans le buffer. Les récepteurs les obtiennent d'abord, puis chaque réception rend la main immédiatement avec la valeur zéro :
example.gogofunc main() { events := make(chan string, 2) events <- "user.created" events <- "user.deleted" close(events) for i := 0; i < 3; i++ { ev, ok := <-events fmt.Printf("%q %v\n", ev, ok) } }
example.texttext"user.created" true "user.deleted" true "" false
La seconde valeur, ok, vaut true pour une vraie valeur et false une fois le channel fermé et vidé. Un simple ev := <-events ne peut pas distinguer une chaîne vide réellement envoyée de la valeur zéro d'un channel fermé. Utilisez donc la forme à deux valeurs dès que le channel est susceptible d'être fermé.
for v := range ch fait la même vérification pour vous. La boucle reçoit jusqu'à ce que le channel soit fermé et vide, puis se termine. C'est ainsi que deliver plus haut sait quand s'arrêter, et c'est pourquoi produce doit fermer jobs quand il n'a plus d'identifiants. Sans la fermeture, deliver attendrait indéfiniment un quatrième job.
Qui doit fermer un channel Go ?
La goroutine qui envoie. Le récepteur ne peut pas savoir si une autre valeur est en route, et fermer depuis le côté réception mène tout droit aux panics ci-dessous. Quand plusieurs goroutines envoient sur un même channel, aucune ne peut le fermer seule. Une goroutine séparée les attend toutes avec un sync.WaitGroup puis ferme le channel. Le worker pool plus bas montre cette structure.
Trois erreurs provoquent un panic à l'exécution :
example.texttextpanic: send on closed channel panic: close of closed channel panic: close of nil channel
Vous n'êtes pas obligé de fermer chaque channel. Un channel que plus rien ne référence est libéré par le garbage collector, qu'il ait été fermé ou non. Fermez un channel quand les récepteurs doivent savoir que le flux est terminé, par exemple pour une boucle range ou un signal d'arrêt.
Que se passe-t-il quand on envoie sur un channel nil ou fermé ?
Voici toutes les combinaisons au même endroit :
| Opération | Channel nil | Channel ouvert | Channel fermé |
|---|---|---|---|
Envoi ch <- v | Bloque indéfiniment | Bloque jusqu'à ce qu'un récepteur prenne la valeur ou qu'une place se libère dans le buffer | Panic |
Réception <-ch | Bloque indéfiniment | Renvoie une valeur du buffer, ou bloque jusqu'à ce qu'une valeur arrive | Renvoie les valeurs du buffer, puis la valeur zéro avec ok == false |
close(ch) | Panic | Réussit | Panic |
Un case de select sur un channel nil n'est jamais choisi, et l'exemple de fusion plus bas s'appuie sur ce comportement. Un channel fermé rend la main immédiatement à chaque récepteur, ce qui fait de close un mécanisme de diffusion. Toutes les goroutines qui reçoivent sur le channel se réveillent en même temps.
Pourquoi Go affiche-t-il « all goroutines are asleep - deadlock! » ?
Parce que le runtime a constaté que toutes les goroutines du programme sont bloquées et qu'aucune ne pourra jamais se réveiller. La version la plus simple est un envoi sur un channel non bufferisé sans récepteur :
example.gogofunc main() { uploads := make(chan string) uploads <- "invoice-1001.pdf" fmt.Println("queued") }
example.texttextfatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.main() /app/main.go:7 +0x34
main est la seule goroutine, et elle attend sur un envoi que rien ne recevra. La trace nomme l'opération bloquée (chan send) et la ligne. Pour corriger, recevez dans une autre goroutine, ou donnez un buffer au channel si vous n'avez besoin d'y déposer qu'une seule valeur. Les erreurs courantes en Go en fait la dixième erreur de sa liste.
Le détecteur ne se déclenche que lorsque toutes les goroutines sont coincées. Un serveur web a toujours une goroutine qui attend la prochaine connexion dans le network poller, et le détecteur ne la considère pas comme coincée. Un handler bloqué sur un channel ne le déclenche donc jamais. Le handler s'arrête simplement, et sa goroutine reste en mémoire jusqu'à la fin du processus. C'est ce qu'on appelle une fuite de goroutine.
Comment détecter les fuites de goroutines en Go ?
Une fuite courante ressemble à ceci. Une page de paiement demande un devis à trois transporteurs et retient la réponse la plus rapide :
example.gogofunc fastestQuote(ctx context.Context) (Quote, error) { ch := make(chan Quote) go func() { ch <- fetchQuote("ups", 50*time.Millisecond) }() go func() { ch <- fetchQuote("dhl", 80*time.Millisecond) }() go func() { ch <- fetchQuote("fedex", 300*time.Millisecond) }() select { case q := <-ch: return q, nil case <-ctx.Done(): return Quote{}, ctx.Err() } }
La fonction reçoit une fois puis retourne. Les deux goroutines les plus lentes tentent alors d'envoyer sur un channel non bufferisé que personne ne lira jamais. Elles restent bloquées tant que le processus tourne. Après 100 paiements, 200 goroutines sont coincées.
Go 1.27 a rendu le profil goroutineleak disponible pour tous, après une version à l'état expérimental dans Go 1.26 (notes de version de Go 1.27). Le garbage collector recherche les goroutines bloquées sur un channel ou un verrou qui n'est plus accessible depuis aucune goroutine exécutable, et les signale par pile d'appels :
example.gogopprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1)
example.texttextgoroutineleak profile: total 200 100 @ 0x1044f99a8 0x104492854 0x104492468 0x104550560 0x1044ffc74 # 0x10455055f main.fastestQuote.func3+0x4f /app/main.go:27 100 @ 0x1044f99a8 0x104492854 0x104492468 0x1045505d0 0x1044ffc74 # 0x1045505cf main.fastestQuote.func2+0x4f /app/main.go:26
Le profil désigne les goroutines DHL et FedEx par leur ligne. Un serveur qui importe net/http/pprof expose les mêmes données sur /debug/pprof/goroutineleak. Les nouveautés de Go 1.27 présente ce profil plus en détail.
La correction consiste à prévoir un buffer assez grand pour chaque émetteur :
example.gogoch := make(chan Quote, 3)
Chaque goroutine peut maintenant envoyer puis se terminer, que quelqu'un lise sa valeur ou non. Avec ce changement, le même programme affiche goroutineleak profile: total 0. Plus généralement, chaque goroutine que vous lancez doit avoir un moyen de se terminer. En pratique, c'est un récepteur qui lit toujours, un buffer assez grand pour tous les envois ou un select sur ctx.Done().
Comment select fonctionne-t-il avec les channels en Go ?
select attend sur plusieurs opérations de channel et exécute la première qui peut avancer. Si plusieurs sont prêtes en même temps, il en choisit une au hasard, pour qu'aucun case ne puisse affamer les autres (Go spec).
Comment ajouter un timeout à une réception sur un channel ?
Placez la réception et un channel de timeout dans le même select. Avec un context.Context, le channel de timeout est ctx.Done() :
example.gogofunc lookupPrice(ctx context.Context, sku string) (int, error) { result := make(chan int, 1) go func() { result <- slowPriceService(sku) }() select { case cents := <-result: return cents, nil case <-ctx.Done(): return 0, fmt.Errorf("price for %s: %w", sku, ctx.Err()) } } func main() { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() fmt.Println(lookupPrice(ctx, "SKU-1001")) fmt.Println(lookupPrice(context.Background(), "SKU-1001")) }
example.texttext0 price for SKU-1001: context deadline exceeded 4999 <nil>
slowPriceService prend 300ms, donc le premier appel abandonne au bout de 100ms et le second attend la réponse. Le buffer de 1 sur result est la correction de fuite de la section précédente. Après un timeout, la goroutine peut toujours envoyer sa réponse tardive puis se terminer.
case <-time.After(2 * time.Second): fonctionne aussi quand il n'y a pas de context. Depuis Go 1.23, un timer que plus rien ne référence peut être libéré par le garbage collector avant de se déclencher, donc time.After dans une boucle n'accumule plus de timers (notes de version de Go 1.23). Go 1.27 a supprimé le réglage GODEBUG asynctimerchan qui rétablissait l'ancien comportement. Dans le code qui traite des requêtes, préférez le context, car il transporte aussi l'annulation venant du client.
Comment faire un envoi ou une réception non bloquants ?
Ajoutez un case default. select exécute default quand aucun autre case n'est prêt, si bien que l'opération n'attend jamais. Un exportateur de métriques qui ne doit jamais ralentir le traitement des requêtes s'en sert pour abandonner des échantillons quand sa file est pleine :
example.gogotype Exporter struct { queue chan Metric dropped atomic.Int64 } func (e *Exporter) Record(m Metric) { select { case e.queue <- m: default: e.dropped.Add(1) } } func main() { e := &Exporter{queue: make(chan Metric, 3)} for i := range 5 { e.Record(Metric{Name: "http_requests_total", Value: float64(i)}) } fmt.Println("queued:", len(e.queue), "dropped:", e.dropped.Load()) }
example.texttextqueued: 3 dropped: 2
Record s'exécute dans de nombreux handlers de requêtes à la fois, c'est pourquoi le compteur d'abandons est un atomic.Int64 plutôt qu'un simple int. Gardez le compte des échantillons abandonnés, car un default silencieux masque le moment où le consommateur prend du retard.
Pourquoi mettre un channel à nil dans un select ?
Pour désactiver un case. Une réception sur un channel nil bloque indéfiniment, donc un case de select sur un channel nil ne peut jamais être choisi. Quand vous fusionnez deux flux, mettez chacun à nil une fois qu'il est fermé, et arrêtez-vous quand les deux le sont :
example.gogofunc merge(a, b <-chan string) []string { var out []string for a != nil || b != nil { select { case v, ok := <-a: if !ok { a = nil continue } out = append(out, v) case v, ok := <-b: if !ok { b = nil continue } out = append(out, v) } } return out }
Sans l'affectation à nil, un channel fermé est toujours prêt, et la boucle tournerait sur des valeurs zéro en consommant tout le CPU.
Quels sont les patterns de channels courants en Go ?
Quatre patterns couvrent l'essentiel du code à base de channels dans les services en production.
Worker pool
Un nombre fixe de goroutines lit depuis un même channel de jobs. Celui-ci livre des webhooks avec trois workers :
example.gogofunc main() { hooks := make(chan Webhook) deliveries := make(chan Delivery) var wg sync.WaitGroup for worker := range 3 { wg.Go(func() { for h := range hooks { deliveries <- Delivery{WebhookID: h.ID, Worker: worker, Status: send(h)} } }) } go func() { for id := 1; id <= 9; id++ { hooks <- Webhook{ID: id, URL: fmt.Sprintf("https://partner.example.com/hooks/%d", id)} } close(hooks) }() go func() { wg.Wait() close(deliveries) }() ok := 0 for d := range deliveries { if d.Status == 200 { ok++ } } fmt.Println("delivered:", ok) }
example.texttextdelivered: 9
Quatre détails le rendent correct. Le producteur ferme hooks, donc la boucle range de chaque worker se termine. wg.Go, ajouté dans Go 1.25, lance chaque worker et le suit en un seul appel. Une goroutine séparée attend tous les workers avant de fermer deliveries, car aucun worker ne sait à lui seul quand les autres ont fini. Les closures capturent worker sans risque depuis que Go 1.22 donne à chaque itération de boucle sa propre variable (voir le fonctionnement des closures).
Fan-out et fan-in
Le worker pool relève du fan-out : un channel alimente de nombreuses goroutines. Le channel deliveries relève du fan-in : de nombreuses goroutines écrivent dans un channel qu'une seule boucle lit. La fonction merge ci-dessus fait du fan-in pour un nombre fixe de channels d'entrée. L'article sur les pipelines du blog Go construit des chaînes plus longues avec ces deux mêmes briques.
Sémaphore
Un channel bufferisé de structs vides limite le nombre de goroutines qui font quelque chose en même temps. Par exemple, un service d'images qui ne doit pas redimensionner plus de trois images en parallèle :
example.gogosem := make(chan struct{}, 3) var wg sync.WaitGroup for _, img := range images { wg.Go(func() { sem <- struct{}{} defer func() { <-sem }() resizeImage(img) }) } wg.Wait()
La quatrième goroutine bloque sur sem <- struct{}{} jusqu'à ce que l'une des trois premières libère sa place. Le modèle mémoire le garantit pour un channel de capacité C : la k-ième réception a lieu avant la fin du (k+C)-ième envoi (modèle mémoire de Go). Quand les goroutines renvoient aussi des erreurs, errgroup.Group avec SetLimit de golang.org/x/sync fait le même travail et récupère la première erreur.
Signal d'arrêt avec chan struct{}
Un channel qu'on ne fait que fermer, sans jamais rien envoyer dessus, diffuse un signal à tous ses récepteurs. Le type des éléments est struct{} parce qu'il occupe zéro octet et montre clairement qu'aucune donnée ne circule (voir le mot-clé struct en Go) :
example.gogofunc flushLoop(done <-chan struct{}, flushed chan<- int) { ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() n := 0 for { select { case <-ticker.C: n++ case <-done: flushed <- n return } } } func main() { done := make(chan struct{}) flushed := make(chan int) go flushLoop(done, flushed) time.Sleep(175 * time.Millisecond) close(done) fmt.Println("flushes before shutdown:", <-flushed) }
example.texttextflushes before shutdown: 3
context.Context repose sur la même idée. ctx.Done() renvoie un <-chan struct{} qui est fermé lors de l'annulation, il fonctionne donc dans n'importe quel select exactement comme done ci-dessus.
Faut-il utiliser un channel ou un mutex en Go ?
Utilisez un channel pour transférer la propriété de données ou pour coordonner des goroutines. Utilisez un sync.Mutex quand plusieurs goroutines modifient sur place un même état. Le proverbe Go « Don't communicate by sharing memory, share memory by communicating » (Go Proverbs) décrit le premier cas, et la page du wiki Go Use a sync.Mutex or a channel? dit explicitement que ce proverbe n'exclut pas les mutex.
Un compteur de requêtes par route est un état, pas un flux de valeurs :
example.gogotype Stats struct { mu sync.Mutex counts map[string]int } func (s *Stats) Inc(route string) { s.mu.Lock() defer s.mu.Unlock() s.counts[route]++ }
La version à base de channel nécessite une goroutine propriétaire de la map, un channel de requêtes et un channel de réponse pour les lectures. C'est plus de code, et chaque incrément doit attendre qu'une autre goroutine le traite. Pour une file de jobs, c'est l'inverse. Une slice protégée par un mutex a besoin d'un sync.Cond ou de polling pour faire attendre les workers, alors qu'un channel les bloque et les réveille sans effort supplémentaire.
| Utilisez un channel pour | Utilisez un mutex pour |
|---|---|
| Transmettre un job ou un résultat à une autre goroutine | Les compteurs, caches et maps modifiés sur place |
| Signaler un arrêt, une annulation ou une fin de traitement | Protéger quelques champs d'un struct |
| Limiter la concurrence ou construire des pipelines | Les sections critiques courtes sur les chemins critiques en performance |
Testez les deux avec go test -race. Il signale une map partagée sans verrou, et aussi un émetteur qui continue de modifier un struct après avoir envoyé un pointeur vers celui-ci pendant que le récepteur le lit.
La place de LevelUpGo
LevelUpGo enseigne Go à travers des exercices qui exécutent du vrai code Go dans le navigateur. Go Concurrency Fundamentals couvre les goroutines, les channels non bufferisés et bufferisés, les directions de channel, select, context et le race detector, avec des exercices qui restent en deadlock tant que vous ne les avez pas corrigés. Go Design Patterns reprend sous forme d'exercices complets le worker pool, le fan-out et fan-in et l'arrêt propre présentés dans cet article. Le Training Ground propose de courts exercices autonomes de concurrence, avec du code qui se bloque, fuit ou perd du travail. Pour les 24 autres mots réservés, consultez Les mots-clés Go : les 25 expliqués.
Questions fréquentes
chan est-il un mot-clé en Go ?
Oui. chan fait partie des 25 mots-clés réservés de Go, vous ne pouvez donc pas l'utiliser comme nom de variable ou de fonction. Il apparaît dans les types de channel comme chan Job, chan<- Job et <-chan Job. L'opérateur <- n'est pas un mot-clé, pas plus que make, qui est une fonction prédéclarée.
Faut-il obligatoirement fermer un channel en Go ?
Non. Ne fermez un channel que lorsque les récepteurs doivent savoir qu'aucune autre valeur n'arrivera, par exemple pour terminer une boucle for range ou diffuser un arrêt. Un channel non fermé que plus rien ne référence est libéré par le garbage collector comme n'importe quelle autre valeur.
Quelle est la différence entre un channel bufferisé et un channel non bufferisé ?
Un channel non bufferisé, make(chan T), fait attendre l'émetteur jusqu'à ce qu'un récepteur prenne la valeur, si bien que les deux goroutines se rejoignent. Un channel bufferisé, make(chan T, n), laisse jusqu'à n valeurs attendre dans le channel, et l'émetteur ne bloque que lorsque le buffer est plein.
Que se passe-t-il quand on lit un channel fermé en Go ?
Vous obtenez d'abord les valeurs encore présentes dans le buffer. Ensuite, chaque réception rend la main immédiatement avec la valeur zéro du type des éléments. La forme à deux valeurs v, ok := <-ch met ok à false pour ces valeurs zéro, ce qui permet de les distinguer des vraies valeurs.
Comment savoir si un channel est fermé en Go ?
Recevez depuis le channel avec v, ok := <-ch et vérifiez ok. Aucune fonction n'indique si un channel est fermé sans effectuer de réception, et len(ch) ne compte que les valeurs du buffer. Un code qui a besoin d'une telle vérification a généralement plus d'une goroutine qui ferme le channel, et c'est cette conception qu'il faut corriger.
Peut-on utiliser range sur un channel en Go ?
Oui. for v := range ch reçoit des valeurs jusqu'à ce que le channel soit fermé et vide, puis sort de la boucle. Si personne ne ferme le channel, la boucle attend indéfiniment. L'émetteur doit donc appeler close quand il a terminé.
Sources
- The Go Programming Language Specification, Channel types : https://go.dev/ref/spec#Channel_types
- The Go Programming Language Specification, Send statements : https://go.dev/ref/spec#Send_statements
- The Go Programming Language Specification, Receive operator : https://go.dev/ref/spec#Receive_operator
- The Go Programming Language Specification, Close : https://go.dev/ref/spec#Close
- The Go Programming Language Specification, Select statements : https://go.dev/ref/spec#Select_statements
- The Go Memory Model, Channel communication : https://go.dev/ref/mem#chan
- Effective Go, Concurrency : https://go.dev/doc/effective_go#concurrency
- The Go Blog, Go Concurrency Patterns: Pipelines and cancellation : https://go.dev/blog/pipelines
- Go Wiki, Use a sync.Mutex or a channel? : https://go.dev/wiki/MutexOrChannel
- Go 1.23 Release Notes, Timer changes : https://go.dev/doc/go1.23#timer-changes
- Go 1.27 Release Notes : https://go.dev/doc/go1.27
- Go Proverbs : https://go-proverbs.github.io/
