Go并发编程模式与实战技巧:从Goroutine到Channel的深度实践
Go并发编程模式与实战技巧:从Goroutine到Channel的深度实践
大家好,我是迪哥。Go 的并发模型是它最迷人的特性之一,从 Goroutine 到 Channel,从 Select 到 Context,掌握这些模式能让你的代码飞起来。今天就聊聊 Go 并发编程的最佳实践。
并发基础
Goroutine
// 启动一个 Goroutine go func() { fmt.Println("Hello from goroutine") }() // 带参数的 Goroutine go sayHello("World") func sayHello(name string) { fmt.Printf("Hello, %s!\n", name) }Channel
// 有缓冲 Channel ch := make(chan int, 100) // 发送数据 ch <- 42 // 接收数据 val := <-ch // 关闭 Channel close(ch)Select
select { case <-ch1: fmt.Println("Received from ch1") case <-ch2: fmt.Println("Received from ch2") case <-time.After(1 * time.Second): fmt.Println("Timeout") }并发模式
模式一:Worker Pool
func worker(id int, jobs <-chan int, results chan<- int) { for job := range jobs { fmt.Printf("Worker %d processing job %d\n", id, job) results <- job * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) // 启动 3 个 worker for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 发送 9 个任务 for j := 1; j <= 9; j++ { jobs <- j } close(jobs) // 收集结果 for r := 1; r <= 9; r++ { <-results } }模式二:Fan-Out / Fan-In
// Fan-Out:多个 Goroutine 消费同一个 Channel func produce(ch chan<- int) { for i := 0; i < 10; i++ { ch <- i } close(ch) } func consume(id int, in <-chan int, out chan<- int) { for num := range in { out <- num * 2 } } // Fan-In:多个 Channel 的结果汇总到一个 Channel func merge(cs ...<-chan int) <-chan int { var wg sync.WaitGroup out := make(chan int) wg.Add(len(cs)) for _, c := range cs { go func(ch <-chan int) { for n := range ch { out <- n } wg.Done() }(c) } go func() { wg.Wait() close(out) }() return out }模式三:Pipeline
func gen(nums ...int) <-chan int { out := make(chan int) go func() { for _, n := range nums { out <- n } close(out) }() return out } func sq(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { out <- n * n } close(out) }() return out } func main() { // 生成 → 平方 → 打印 for n := range sq(gen(1, 2, 3, 4)) { fmt.Println(n) } }模式四:Context 取消
func doWork(ctx context.Context) error { for { select { case <-ctx.Done(): return ctx.Err() default: // 正常工作 time.Sleep(100 * time.Millisecond) } } } func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() go doWork(ctx) }并发安全
Mutex
type Counter struct { mu sync.Mutex value int } func (c *Counter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.value++ } func (c *Counter) Value() int { c.mu.Lock() defer c.mu.Unlock() return c.value }Sync.Map
var cache sync.Map func getFromCache(key string) (interface{}, bool) { return cache.Load(key) } func setToCache(key string, value interface{}) { cache.Store(key, value) }性能优化
Sync.Pool
var bufferPool = sync.Pool{ New: func() interface{} { return make([]byte, 4096) }, } func processData(data []byte) { buf := bufferPool.Get().([]byte) defer bufferPool.Put(buf) copy(buf, data) // 处理... }避免 Goroutine 泄漏
// ❌ 泄漏:没有停止机制 go func() { for { doSomething() } }() // ✅ 正确:使用 Channel 控制 stop := make(chan struct{}) go func() { for { select { case <-stop: return default: doSomething() } } }() close(stop) // 停止 Goroutine常见坑
| 坑 | 原因 | 解决方案 |
|---|---|---|
| Goroutine 泄漏 | 没有退出机制 | 使用 Context 或 Channel 控制 |
| 死锁 | Channel 发送/接收不匹配 | 确保发送和接收配对 |
| 数据竞争 | 共享数据未加锁 | 使用 Mutex 或 Channel |
| 内存泄漏 | Sync.Pool 误用 | 正确使用 Pool,避免存储指针 |
| 阻塞主协程 | 忘记启动 Goroutine | 检查 go 关键字 |
说到并发,我家那只叫 Docker 的哈士奇最近学会了"并发抢食"——两只碗同时吃,效率翻倍,这并发能力比某些程序员还强 😂
我是迪哥,我们下期再见!
