Understanding Goroutines and Channels
What Are Goroutines?
A goroutine is a lightweight thread managed by the Go runtime. They’re cheap to create — you can spawn thousands of them without breaking a sweat. Just prefix any function call with go:
func sayHello() {
fmt.Println("Hello from goroutine!")
}
func main() {
go sayHello()
time.Sleep(time.Second) // give it time to finish
}
The Power of Channels
Channels are the pipes that connect goroutines. They let you safely pass data between concurrent operations without explicit locks.
func main() {
ch := make(chan string)
go func() {
ch <- "Hello from goroutine!"
}()
msg := <-ch
fmt.Println(msg)
}
Buffered vs Unbuffered Channels
Unbuffered channels block until both sender and receiver are ready — they’re a synchronization primitive. Buffered channels let you queue up values:
// Unbuffered: synchronous
ch := make(chan int)
// Buffered: capacity of 3
ch := make(chan int, 3)
Select Statement
The select statement lets a goroutine wait on multiple channel operations:
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
case <-time.After(time.Second):
fmt.Println("Timeout!")
}
Common Patterns
Worker Pool
Limit concurrency by using a pool of worker goroutines:
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
}
Key Takeaways
- Goroutines are cheap, lightweight threads
- Channels enable safe communication between goroutines
- Use
selectto handle multiple channels - Always make sure channels are properly closed to avoid goroutine leaks