Backend Development 12 min read

Master Go Channels: From Basics to Advanced Patterns

This article explains Go's channel primitive in depth, covering its conceptual model, types, operations, internal queues, rule scenarios, practical code examples, and best‑practice tips for building clear and efficient concurrent programs.

360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
Master Go Channels: From Basics to Advanced Patterns

Channel Introduction

Go provides two built‑in concurrency primitives—goroutine and channel. Channels enable safe data exchange and synchronization between goroutines, following the principle “don’t communicate by sharing memory; share memory by communicating.”

Channel Classification

A channel is a composite type with an element type; it can be nil. Channels come in three forms:

chan T – bidirectional channel, allowing both send and receive.

chan<- T – send‑only channel.

&lt;-chan T – receive‑only channel.

Channels are created with the built‑in make function, optionally specifying a capacity (default 0 for unbuffered).

Channel Operations

Close a channel using the built‑in close function (cannot close a receive‑only channel).

Send a value to a channel with the &lt;- operator.

Receive a value from a channel with the &lt;- operator; the operation returns the element and can be used in an assignment.

Query a channel’s capacity with the built‑in cap function.

Query the number of elements currently buffered with the built‑in len function.

Channel Rule Summary

Channels are categorized into four scenarios:

nil channel

non‑nil but closed channel

non‑nil and open channel

Key rules derived from these scenarios include:

If a channel is closed, its send‑queue and receive‑queue must be empty, but its buffer may contain values.

If the buffer queue (VBQ) is non‑empty, the receive‑queue (RGQ) must be empty.

If the buffer is not full, the send‑queue (SGQ) must be empty.

For buffered channels, either SGQ or RGQ is empty at any time; for unbuffered channels the same holds except when select may place a goroutine in both queues.

Channel Usage Examples

Unbuffered channel example:

<code>package main
import "fmt"
func main() {
    c := make(chan int) // unbuffered
    go func() {
        x := <-c               // blocks until a value is sent
        c <- x * x             // blocks until the value is received
    }()
    c <- 3                     // blocks until the goroutine receives
    y := <-c
    fmt.Println(y) // prints 9
}
</code>

Buffered channel example (non‑concurrent):

<code>package main
import "fmt"
func main() {
    c := make(chan int, 2) // buffered with capacity 2
    c <- 3
    c <- 5
    close(c)
    fmt.Println(len(c), cap(c)) // 2 2
    x, ok := <-c
    fmt.Println(x, ok) // 3 true
    fmt.Println(len(c), cap(c)) // 1 2
    x, ok = <-c
    fmt.Println(x, ok) // 5 true
    fmt.Println(len(c), cap(c)) // 0 2
    x, ok = <-c
    fmt.Println(x, ok) // 0 false
    close(c) // panic!
    c <- 7   // also panic if the previous line is removed
}
</code>

Continuous “football match” example demonstrating blocking on a nil channel:

<code>package main
import (
    "fmt"
    "time"
)
func main() {
    var ball = make(chan string)
    kickBall := func(playerName string) {
        for {
            fmt.Println(<-ball, "kicked the ball.")
            time.Sleep(time.Second)
            ball <- playerName
        }
    }
    go kickBall("John")
    go kickBall("Alice")
    go kickBall("Bob")
    go kickBall("Emily")
    ball <- "referee" // kick off
    var c chan bool // nil channel
    <-c // blocks forever
}
</code>

Channel Element Copy Semantics

Both sending to and receiving from a channel copy the element, similar to assignment or function argument passing. The Go compiler limits channel element types to a maximum size of 65535 bytes. Large values should be passed by pointer to avoid excessive copying.

For‑Range Loop Over Channels

A for‑range loop can iterate over values received from a channel until the channel is closed and its buffer is empty:

<code>for v := range aChannel {
    // use v
}
</code>

This construct is equivalent to:

<code>for {
    v, ok := <-aChannel
    if !ok {
        break
    }
    // use v
}
</code>

If the channel is nil, the loop blocks forever. The loop cannot be used with a send‑only channel.

ConcurrencyGogo programminggoroutinechannel
360 Zhihui Cloud Developer
Written by

360 Zhihui Cloud Developer

360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.