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!