Channels (Recommended)
Channels are Go's preferred way to communicate between goroutines.
Unbuffered Channels
goch := make(chan int) // Synchronous communication // Sender blocks until receiver receives go func() { ch <- 42 // Send (blocks until received) }() value := <-ch // Receive (blocks until sent) fmt.Println(value) // 42
Visualization:
codeGoroutine 1 Channel Goroutine 2 │ │ │ │ ch <- 42 │ │ │──────────────────►│ │ │ (blocks) │ │ │ │ value := <-ch │ │ │◄──────────────────│ │ (unblocks) │ (receives 42) │ │ │ │
Buffered Channels
goch := make(chan int, 3) // Buffer size 3 // Can send 3 values without blocking ch <- 1 ch <- 2 ch <- 3 // ch <- 4 // This would block! fmt.Println(<-ch) // 1 fmt.Println(<-ch) // 2 fmt.Println(<-ch) // 3
Channel Directions
go// Send-only channel func send(ch chan<- int) { ch <- 42 // value := <-ch // Compile error! } // Receive-only channel func receive(ch <-chan int) { value := <-ch // ch <- 42 // Compile error! } func main() { ch := make(chan int) go send(ch) go receive(ch) }
Select Statement
gofunc main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 <- "from ch1" }() go func() { time.Sleep(2 * time.Second) ch2 <- "from ch2" }() for i := 0; i < 2; i++ { select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case <-time.After(3 * time.Second): fmt.Println("timeout") } } }
Mutexes (When Needed)
Use mutexes for protecting shared state:
gotype SafeCounter struct { mu sync.Mutex count map[string]int } func (c *SafeCounter) Inc(key string) { c.mu.Lock() defer c.mu.Unlock() c.count[key]++ } func (c *SafeCounter) Value(key string) int { c.mu.Lock() defer c.mu.Unlock() return c.count[key] } func main() { counter := SafeCounter{count: make(map[string]int)} var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter.Inc("key") }() } wg.Wait() fmt.Println(counter.Value("key")) // 1000 }
Was this helpful?