Web Analytics

Channels

Advanced ~40 min read

Channels are Go's pipes for communication between goroutines. They provide a safe way to send and receive values, following the principle: "Don't communicate by sharing memory; share memory by communicating."

What is a Channel?

A channel is a typed conduit through which you can send and receive values:

Channel Communication Sender Goroutine ch <- value Channel (ch) 42 42 Receiver Goroutine value := <-ch< /text> Safe, synchronized communication No shared memory, no race conditions
Channels provide safe communication between goroutines
Output
Click Run to execute your code
Channel Operations:
  • ch <- value - Send value to channel
  • value := <-ch - Receive value from channel
  • close(ch) - Close channel (sender only)
  • value, ok := <-ch - Receive with closed check

Unbuffered vs Buffered Channels

Unbuffered vs Buffered Channels Unbuffered (Synchronous) Sender ch Receiver ✓ Sender blocks until receiver ready ✓ Guaranteed synchronization ✗ No capacity Buffered (Asynchronous) Sender ch (cap=3) ✓ Sender doesn't block (if space) ✓ Decouples sender/receiver ✓ Has capacity Unbuffered ch := make(chan int) // Capacity: 0 // Send blocks until receive // Receive blocks until send Buffered ch := make(chan int, 3) // Capacity: 3 // Send blocks when full // Receive blocks when empty
Unbuffered channels synchronize; buffered channels decouple
Output
Click Run to execute your code

Channel Direction

You can restrict channels to send-only or receive-only:

// Send-only channel
func sender(ch chan<- int) {
    ch <- 42  // OK
    // val := <-ch  // Error: receive from send-only
}

// Receive-only channel
func receiver(ch <-chan int) {
    val := <-ch  // OK
    // ch <- 42  // Error: send to receive-only
}

func main() {
    ch := make(chan int)  // Bidirectional
    go sender(ch)         // Converts to send-only
    receiver(ch)          // Converts to receive-only
}
Best Practice: Use directional channels in function parameters to make intent clear and prevent misuse. The compiler will catch errors!

Closing Channels

Output
Click Run to execute your code
Important Rules:
  • Only the sender should close a channel
  • Sending to a closed channel causes panic
  • Receiving from a closed channel returns zero value
  • Closing is optional (for signaling completion)

Range over Channels

func producer(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)  // Signal completion
}

func main() {
    ch := make(chan int)
    go producer(ch)
    
    // Range automatically stops when channel closed
    for value := range ch {
        fmt.Println(value)
    }
}

Common Channel Patterns

1. Pipeline Pattern

Pipeline Pattern Generator Produces data ch1 Processor Transforms data ch2 Consumer Uses data Data flows through stages via channels
Pipeline: Chain stages with channels
func generator() <-chan int {
    ch := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        close(ch)
    }()
    return ch
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    nums := generator()
    squares := square(nums)
    
    for result := range squares {
        fmt.Println(result)
    }
}

2. Fan-Out, Fan-In

// Fan-out: Multiple workers read from same channel
func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2  // Process job
    }
}

// Fan-in: Merge multiple channels into one
func merge(channels ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    
    for _, ch := range channels {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for v := range c {
                out <- v
            }
        }(ch)
    }
    
    go func() {
        wg.Wait()
        close(out)
    }()
    
    return out
}

Common Mistakes

1. Deadlock - no receiver

// ❌ Wrong - deadlock!
ch := make(chan int)
ch <- 42  // Blocks forever (no receiver)

// ✅ Correct - use goroutine
ch := make(chan int)
go func() {
    ch <- 42
}()
value := <-ch

2. Sending to closed channel

// ❌ Wrong - panic!
ch := make(chan int)
close(ch)
ch <- 42  // panic: send on closed channel

// ✅ Correct - only sender closes
// Never close from receiver side

3. Not closing channels in range

// ❌ Wrong - range never exits
func producer(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    // Forgot to close!
}

// ✅ Correct - close when done
func producer(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)  // Signal completion
}

Exercise: Worker Pool

Task: Create a worker pool using channels.

Requirements:

  • Create 3 workers that process jobs concurrently
  • Send 10 jobs through a jobs channel
  • Collect results through a results channel
  • Print which worker processed each job
Show Solution
package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- string) {
    for job := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, job)
        time.Sleep(time.Millisecond * 500)  // Simulate work
        result := fmt.Sprintf("Worker %d completed job %d (result: %d)", id, job, job*2)
        results <- result
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3
    
    jobs := make(chan int, numJobs)
    results := make(chan string, numJobs)
    
    // Start workers
    for w := 1; w <= numWorkers; w++ {
        go worker(w, jobs, results)
    }
    
    // Send jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    // Collect results
    for a := 1; a <= numJobs; a++ {
        fmt.Println(<-results)
    }
}

Summary

  • Channels provide safe communication between goroutines
  • Unbuffered channels synchronize sender and receiver
  • Buffered channels decouple with capacity
  • Directional channels restrict send/receive operations
  • close() signals no more values (sender only)
  • range loops until channel is closed
  • Patterns: pipeline, fan-out, fan-in

What's Next?

Now that you understand channels, you're ready to learn about Select & Patterns. The select statement lets you work with multiple channels, enabling powerful concurrent patterns!