Go 并发编程
并发是 Go 的招牌能力,核心是两件工具:goroutine(轻量级线程,go 关键字启动)和 channel(通信管道)。Go 的格言:不要通过共享内存来通信,而要通过通信来共享内存。(说明:文中的片段代码需放进完整程序里运行)
goroutine 与 sync.WaitGroup
主函数退出程序就结束,所以要用 sync.WaitGroup 等待 goroutine 跑完。完整示例:
package main
import (
"fmt"
"sync"
)
func work(id int, wg *sync.WaitGroup) {
defer wg.Done() // 完工:计数减 1
fmt.Println("任务", id, "完成")
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 登记:计数加 1
go work(i, &wg) // 启动 goroutine
}
wg.Wait() // 阻塞直到计数归零
fmt.Println("全部完成")
}
// 输出(三个任务的顺序每次可能不同):
// 任务 2 完成
// 任务 1 完成
// 任务 3 完成
// 全部完成
Add(登记)、Done(完工)、Wait(等待)配套使用;goroutine 的执行顺序不保证。
channel 基本用法:无缓冲与有缓冲
channel 传递数据:发送 ch <- v,接收 v := <-ch。无缓冲 channel 收发必须同时就绪(同步握手);有缓冲 make(chan int, n) 在缓冲未满时发送不阻塞:
ch := make(chan int) // 无缓冲
go func() { ch <- 42 }() // 放在别的 goroutine 里发,防止互相等待死锁
fmt.Println(<-ch) // 42
buf := make(chan int, 2) // 有缓冲
buf <- 1
buf <- 2
fmt.Println(<-buf) // 1:先进先出
用完可 close(ch) 关闭,遵循"谁创建谁负责关闭"。
select 多路选择
select 同时等待多个 channel 操作,哪个就绪执行哪个;配 time.After 可以加超时兜底:
a := make(chan string, 1)
a <- "A"
select {
case msg := <-a:
fmt.Println("来自 a:", msg)
case <-time.After(2 * time.Second): // 超时
fmt.Println("超时")
}
// 输出: 来自 a: A
sync.Mutex 互斥锁示例
多个 goroutine 同时写同一个变量会数据竞争,用 sync.Mutex 加锁保护临界区:
var mu sync.Mutex
counter := 0
// 1000 个 goroutine 里各执行一次:
mu.Lock() // 加锁
counter++ // 临界区:同一时刻只有一个 goroutine 进入
mu.Unlock() // 解锁
fmt.Println("counter =", counter) // 结果一定是 1000
去掉锁 counter 通常会小于 1000;运行 go run -race 可以检测数据竞争,并发代码建议都跑一遍。
关于 GOMAXPROCS(一句话)
GOMAXPROCS 决定同时执行 goroutine 的 CPU 核数,Go 1.5 起默认等于机器核数,绝大多数场景无需手动设置;可用 runtime.GOMAXPROCS(0) 查询当前值。
小结:go 启动并发,WaitGroup 等待,channel 传数据,select 多路复用,Mutex 保护共享变量;配合 -race 检查竞争。