# Goroutines
A goroutine is a lightweight thread managed by the Go runtime. Start one with the go keyword:
package main
import (
"fmt"
"time"
)
func sayHello(name string) {
fmt.Println("Hello,", name)
}
func main() {
go sayHello("Alice") // runs concurrently
go sayHello("Bob") // runs concurrently
time.Sleep(100 * time.Millisecond)
fmt.Println("done")
}⚠️ Don't rely on time.Sleep — use channels or sync.WaitGroup to synchronize.
# Channels
Channels are Go's primary mechanism for goroutine communication. They're typed conduits through which you send and receive values:
// Unbuffered channel
ch := make(chan string)
go func() {
ch <- "hello" // send
}()
msg := <-ch // receive (blocks until sent)
fmt.Println(msg) // helloBuffered Channels
// Buffered: holds up to 3 values without blocking
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
// ch <- 4 would block (buffer full)
fmt.Println(<-ch) // 1 (FIFO)
fmt.Println(<-ch) // 2Range over Channel
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // must close for range to exit
}()
for v := range ch {
fmt.Println(v)
}# select — Multiplexing Channels
select waits on multiple channel operations and executes the first one that's ready:
select {
case msg := <-ch1:
fmt.Println("from ch1:", msg)
case msg := <-ch2:
fmt.Println("from ch2:", msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout!")
default:
fmt.Println("nothing ready")
}# sync.WaitGroup & sync.Mutex
import "sync"
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
total := 0
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
mu.Lock()
total += n
mu.Unlock()
}(i)
}
wg.Wait() // block until all goroutines done
fmt.Println(total) // 15
}# Pattern: Fan-out / Fan-in
A common concurrency pattern: fan-out work to multiple goroutines, fan-in results through a single channel:
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("worker %d processing job %d\n", id, j)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
// Fan-out: 3 workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Send 5 jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// Fan-in: collect results
for r := 1; r <= 5; r++ {
fmt.Println(<-results)
}
}⚡ Key Takeaways
- Goroutines are extremely cheap — spawn thousands without worry
- Channels synchronize goroutines — unbuffered channels block until both sides are ready
selectmultiplexes channels — great for timeouts and cancellation- Use
sync.WaitGroupto wait for goroutine completion - Use
sync.Mutexor channels for shared state — never access shared data without synchronization - Use
go run -raceto detect race conditions