Channels
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)
}hello
worldBuffered 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)
}buffered
channel
Length: 3
Capacity: 3
1
2
3Channel 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")
}Working...
Done
Worker finishedChannel 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)
}Received: hello
Received: worldSelect 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)
}
}
}Received one
Received twoChannel Operations
| Operation | Syntax | Description |
|---|---|---|
| Create | make(chan Type) | Unbuffered channel |
| Create buffered | make(chan Type, size) | Buffered channel |
| Send | ch <- value | Send value to channel |
| Receive | value := <-ch | Receive value from channel |
| Receive with ok | value, ok := <-ch | Check if channel is closed |
| Close | close(ch) | Close channel (no more sends) |
| Range | for v := range ch { } | Iterate until channel closed |
| Length | len(ch) | Number of queued elements |
| Capacity | cap(ch) | Buffer size |
Channel Types
| Type | Syntax | Use Case |
|---|---|---|
| Bidirectional | chan Type | Send and receive |
| Send-only | chan<- Type | Only send values |
| Receive-only | <-chan Type | Only receive values |
| Unbuffered | make(chan Type) | Synchronous communication |
| Buffered | make(chan Type, n) | Asynchronous up to capacity |
Select Statement Patterns
| Pattern | Example | Use Case |
|---|---|---|
| Multiple channels | select { case <-ch1: case <-ch2: } | Wait on multiple operations |
| Default case | select { case <-ch: default: } | Non-blocking receive |
| Timeout | case <-time.After(1 * time.Second): | Time-bounded operation |
| Done channel | case <-done: | Cancellation signal |
Channel Characteristics
| Feature | Description | Details |
|---|---|---|
| Type-safe | Channels are strongly typed | Only specific type can be sent/received |
| Blocking | Operations block by default | Send blocks until receiver ready |
| Synchronization | Provides implicit synchronization | No need for explicit locks |
| First-class values | Can be passed, stored, returned | Like any other value |
| Zero value is nil | nil channels block forever | Must use make() |
| Closable | Can signal no more values | Receivers can detect closure |
Best Practices
| Practice | Description | Reason |
|---|---|---|
| Sender closes | Only sender should close channels | Closing closed channel panics |
| Check closure | Use v, ok := <-ch | Detect when channel closed |
| Use select for timeouts | Combine with time.After | Prevent indefinite blocking |
| Buffered for known size | Use when exact count known | Avoid goroutine blocking |
| Don't pass by value | Channels are references | No need for pointers |
| Context for cancellation | Use context.Context | Standard cancellation pattern |
Common Patterns
| Pattern | Description | Use Case |
|---|---|---|
| Worker pool | Multiple workers on shared channel | Parallel task processing |
| Pipeline | Chain channels for data flow | Multi-stage processing |
| Fan-out | One channel to many workers | Distribute work |
| Fan-in | Merge many channels to one | Collect results |
| Semaphore | Buffered channel for limiting | Limit concurrency |
| Done channel | Signal completion/cancellation | Coordinate shutdown |
| Quit channel | Signal goroutine to stop | Graceful termination |