Go Gopher How to Go

Channels are typed conduits that allow goroutines to communicate and synchronize. They provide a safe way to pass data between concurrent operations, following Go's philosophy: "Don't communicate by sharing memory; share memory by communicating."

💡 Key Points

  • Channels allow goroutines to communicate safely
  • Create channels with make(chan Type)
  • Send to channel: ch <- value
  • Receive from channel: value := <-ch
  • Channels block until both sender and receiver are ready
  • Buffered channels allow limited asynchronous communication
  • Close channels to signal no more values: close(ch)
  • Use range to iterate over channel values

Creating and Using Channels

Channels are created with make and used with the <- operator:

package main

import "fmt"

func main() {
    // Create a channel
    messages := make(chan string)
    
    // Send value in goroutine
    go func() {
        messages <- "hello"
        messages <- "world"
    }()
    
    // Receive values
    msg1 := <-messages
    fmt.Println(msg1)
    
    msg2 := <-messages
    fmt.Println(msg2)
}
Output:
hello
world

Buffered Channels

Buffered channels accept a limited number of values without blocking:

package main

import "fmt"

func main() {
    // Buffered channel with capacity 2
    messages := make(chan string, 2)
    
    // Send without blocking (up to capacity)
    messages <- "buffered"
    messages <- "channel"
    
    // Receive values
    fmt.Println(<-messages)
    fmt.Println(<-messages)
    
    // Demonstrate buffer
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
    
    fmt.Println("Length:", len(ch))
    fmt.Println("Capacity:", cap(ch))
    
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}
Output:
buffered
channel
Length: 3
Capacity: 3
1
2
3

Channel Synchronization

Channels can synchronize execution between goroutines:

package main

import (
    "fmt"
    "time"
)

func worker(done chan bool) {
    fmt.Println("Working...")
    time.Sleep(time.Second)
    fmt.Println("Done")
    
    // Signal completion
    done <- true
}

func main() {
    done := make(chan bool)
    
    go worker(done)
    
    // Block until signal received
    <-done
    fmt.Println("Worker finished")
}
Output:
Working...
Done
Worker finished

Channel Direction

Specify send-only or receive-only channels in function signatures:

package main

import "fmt"

// Send-only channel
func sender(ch chan<- string) {
    ch <- "hello"
    ch <- "world"
    close(ch)
}

// Receive-only channel
func receiver(ch <-chan string) {
    for msg := range ch {
        fmt.Println("Received:", msg)
    }
}

func main() {
    messages := make(chan string, 2)
    
    go sender(messages)
    receiver(messages)
}
Output:
Received: hello
Received: world

Select Statement

Select allows waiting on multiple channel operations:

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()
    
    // Wait for both channels
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("Received", msg1)
        case msg2 := <-c2:
            fmt.Println("Received", msg2)
        }
    }
}
Output:
Received one
Received two

Channel Operations

OperationSyntaxDescription
Createmake(chan Type)Unbuffered channel
Create bufferedmake(chan Type, size)Buffered channel
Sendch <- valueSend value to channel
Receivevalue := <-chReceive value from channel
Receive with okvalue, ok := <-chCheck if channel is closed
Closeclose(ch)Close channel (no more sends)
Rangefor v := range ch { }Iterate until channel closed
Lengthlen(ch)Number of queued elements
Capacitycap(ch)Buffer size

Channel Types

TypeSyntaxUse Case
Bidirectionalchan TypeSend and receive
Send-onlychan<- TypeOnly send values
Receive-only<-chan TypeOnly receive values
Unbufferedmake(chan Type)Synchronous communication
Bufferedmake(chan Type, n)Asynchronous up to capacity

Select Statement Patterns

PatternExampleUse Case
Multiple channelsselect { case <-ch1: case <-ch2: }Wait on multiple operations
Default caseselect { case <-ch: default: }Non-blocking receive
Timeoutcase <-time.After(1 * time.Second):Time-bounded operation
Done channelcase <-done:Cancellation signal

Channel Characteristics

FeatureDescriptionDetails
Type-safeChannels are strongly typedOnly specific type can be sent/received
BlockingOperations block by defaultSend blocks until receiver ready
SynchronizationProvides implicit synchronizationNo need for explicit locks
First-class valuesCan be passed, stored, returnedLike any other value
Zero value is nilnil channels block foreverMust use make()
ClosableCan signal no more valuesReceivers can detect closure

Best Practices

PracticeDescriptionReason
Sender closesOnly sender should close channelsClosing closed channel panics
Check closureUse v, ok := <-chDetect when channel closed
Use select for timeoutsCombine with time.AfterPrevent indefinite blocking
Buffered for known sizeUse when exact count knownAvoid goroutine blocking
Don't pass by valueChannels are referencesNo need for pointers
Context for cancellationUse context.ContextStandard cancellation pattern

Common Patterns

PatternDescriptionUse Case
Worker poolMultiple workers on shared channelParallel task processing
PipelineChain channels for data flowMulti-stage processing
Fan-outOne channel to many workersDistribute work
Fan-inMerge many channels to oneCollect results
SemaphoreBuffered channel for limitingLimit concurrency
Done channelSignal completion/cancellationCoordinate shutdown
Quit channelSignal goroutine to stopGraceful termination