Select and Sync
Select and Sync Primitives in Go
select statement Like a switch for channels — waits on multiple channel operations and executes the first one that is ready:
go
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
case msg := <-ch2:
fmt.Println("ch2:", msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
}sync.WaitGroup Coordinates completion of a group of goroutines:
wg.Add(n)— declare n goroutines to wait for.wg.Done()— call when a goroutine finishes (use defer).wg.Wait()— block until the counter reaches zero.
sync.Mutex Protects shared state from concurrent access:
go
var mu sync.Mutex
mu.Lock()
// critical section
mu.Unlock()Use defer mu.Unlock() immediately after Lock() to ensure the lock is always released.
Key points:
- select with a default case is non-blocking.
- Prefer channels for communication; use mutexes only to protect shared state.
- sync.RWMutex allows multiple concurrent readers.
Code Examples
select statementgo
package main
import (
"fmt"
"time"
)
func ticker(d time.Duration, ch chan<- string, label string) {
for i := 0; i < 3; i++ {
time.Sleep(d)
ch <- fmt.Sprintf("%s-%d", label, i+1)
}
close(ch)
}
func main() {
ch1 := make(chan string, 3)
ch2 := make(chan string, 3)
go ticker(10*time.Millisecond, ch1, "fast")
go ticker(20*time.Millisecond, ch2, "slow")
time.Sleep(70 * time.Millisecond)
for {
select {
case v, ok := <-ch1:
if ok { fmt.Println(v) }
case v, ok := <-ch2:
if ok { fmt.Println(v) }
default:
fmt.Println("done")
return
}
}
}select picks a ready channel at random when multiple are ready. A default case makes it non-blocking.
sync.WaitGroup and sync.Mutexgo
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
func main() {
var wg sync.WaitGroup
counter := &SafeCounter{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final count:", counter.Value())
}Without the mutex, concurrent increments would produce a race condition. WaitGroup ensures we wait for all 100 goroutines.
Quick Quiz
1. What does a `default` case in a `select` statement do?
2. Why should you call `defer mu.Unlock()` right after `mu.Lock()`?
Was this lesson helpful?