Go Concurrency Basics, Libraries, and Best Practices
The article explains Go’s scheduler (M, P, G), demonstrates goroutine, channel, and select usage, covers sync primitives, atomic ops, context cancellation, singleflight, common pitfalls, debugging tools, and a channel‑driven chatroom case study, offering best‑practice guidance for efficient concurrent programming.
This article introduces Go's native and extended concurrency libraries, common pitfalls, and optimization techniques. It starts with the Go scheduler's core structures (M, P, G) and explains how they map user‑space goroutines to kernel threads.
M, P, G are the main scheduler structures. M is a kernel thread that runs goroutines, P is a processor that holds a run queue of Gs, and G is the lightweight goroutine structure containing its stack and program counter.
The article then details the goroutine model and provides several practical examples of using go statements, channels, and select statements for synchronization and communication.
// Create channels a := make(chan int) b := make(chan int, 10) // Unidirectional channels c := make(chan<- int) d := make(<-chan int)
It covers channel operations such as send/receive, closing, ranging over a channel, and using buffered channels.
v, ok := <-a // ok is false if the channel is closed
Advanced patterns include using select for multiplexed I/O, limiting concurrency with semaphore channels, and gathering results from multiple goroutines.
limits := make(chan struct{}, 2) for i := 0; i < 10; i++ { go func() { limits <- struct{}{} do() <-limits }() }
The article also reviews synchronization primitives from the sync package:
Mutex and RWMutex with demo code showing lock contention.
Atomic operations for primitive types (Add, CompareAndSwap, Load, Store, Swap).
WaitGroup and ErrGroup for waiting on a set of goroutines and error propagation.
Once for one‑time initialization.
Example of atomic counter:
var sum uint64 for i := 0; i < 100; i++ { go func() { for c := 0; c < 100; c++ { atomic.AddUint64(&sum, 1) } wg.Done() }() } wg.Wait() fmt.Println(sum)
It discusses context usage for cancellation, deadlines, and value propagation, and highlights a real‑world scenario of cascading cancellations in gRPC calls.
Further, the article introduces singleflight from golang.org/x/sync to suppress duplicate work, with a demo that shares the result of a slow computation among concurrent callers.
v, err, shared := g.Do(key, func() (interface{}, error) { time.Sleep(5 * time.Second) return rand.Intn(100), nil })
Common pitfalls such as goroutine leaks, improper lock handling, and channel blocking are examined, along with debugging tools like runtime.NumGoroutine() , pprof, and third‑party profilers.
Finally, the article presents a case study of replacing a mutex‑protected chatroom implementation with a channel‑driven design, showing how registration, unregistration, and message broadcasting can be safely coordinated without explicit locks.
func (room *Room) ProcessTask() { for { select { case c := <-room.register: room.clientsPool[c] = true case c := <-room.unregister: if room.clientsPool[c] { close(c.send) delete(room.clientsPool, c) } case m := <-room.send: for c := range room.clientsPool { select { case c.send <- m: default: break } } } } }
Overall, the article provides a comprehensive guide to Go concurrency, covering fundamentals, practical patterns, performance considerations, and debugging techniques.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.