~/Essential Channel Patterns for Go Developers

Jul 13, 2019


Go channels provide a mechanism for concurrent communication between goroutines. Developers use channel patterns to manage concurrency, coordinate tasks, and ensure correct data flow. Understanding common patterns is important for effective Go concurrency.

Unbuffered Channel Synchronization

Unbuffered channels allow two goroutines to synchronize at a rendezvous point. No value passes unless both sender and receiver are ready. Useful for signaling or performing simple handshakes.

1
2
3
4
5
6
done := make(chan struct{})
go func() {
    // do work
    done <- struct{}{}
}()
<-done

Fan-Out Fan-In

The fan-out pattern launches workers reading from a single channel, processing input concurrently. Fan-in merges results onto one channel. This allows parallel processing and unified collection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 0; w < 3; w++ {
    go worker(jobs, results)
}
for j := 1; j <= 9; j++ {
    jobs <- j
}
close(jobs)
for a := 1; a <= 9; a++ {
    <-results
}

Channel Directionality

Directional channels restrict read or write operations, making code safer by enforcing channel usage.

1
2
3
4
5
6
func producer(out chan<- int) {
    out <- 1
}
func consumer(in <-chan int) {
    _ = <-in
}

Buffered Channels for Rate Limiting

Buffered channels can implement rate limiting by controlling how many messages pass without blocking.

1
2
3
4
5
rateLimit := time.Tick(time.Millisecond * 100)
for req := range requests {
    <-rateLimit
    // handle request
}

Select Statement for Multiplexing

The select statement waits on multiple channel operations, enabling timeouts, cancellation, event multiplexing, and priority handling.

1
2
3
4
5
6
7
8
select {
case x := <-ch1:
    // handle ch1
case y := <-ch2:
    // handle ch2
case <-time.After(time.Second):
    // timeout
}

Closing Channels

Closing a channel broadcasts a signal to all receivers. Receivers detect closure via a second return value.

1
2
close(ch)
v, ok := <-ch // ok is false if ch is closed

Pipeline Construction

Pipelines connect a series of stages via channels, each running in a goroutine. This improves modularity and parallelism.

1
2
3
4
5
func stage1(out chan<- int) { out <- 1; close(out) }
func stage2(in <-chan int, out chan<- int) {
    for v := range in { out <- v * 2 }
    close(out)
}

Broadcast with Multiple Receivers

Using multiple receivers allows you to send the same message to all listening goroutines by closing a broadcast channel.

1
2
3
4
5
6
7
broadcast := make(chan struct{})
for i := 0; i < n; i++ {
    go func() {
        <-broadcast // unblock upon close
    }()
}
close(broadcast)

Ticker and Timer Channels

Ticker and timer channels integrate with select for periodic actions or timeouts.

1
2
3
4
5
6
7
8
tick := time.NewTicker(time.Second).C
timer := time.NewTimer(time.Minute).C
select {
case <-tick:
    // periodic event
case <-timer:
    // timeout
}

Nil Channel Disabling

Assigning a channel variable to nil disables that case in select, which can be used to control select logic.

1
2
3
4
5
6
var ch chan int
select {
case <-ch: // never selected
default:
    // fallback
}

Understanding and applying these patterns enhances Go concurrency effectiveness and reliability. For deeper discussion and more advanced examples, review Go Concurrency Patterns and Effective Go.

Tags: [golang] [concurrency] [channels]