Go 并发
goroutine、channel、select、WaitGroup、Mutex、以及竞态检测。
Key Pointsgoroutine 是 Go 的并发单元,channel 是它们之间通信的管道;不要用共享内存通信,要通信用共享内存。
goroutine
Go
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("worker %d 完成\n", id)
}
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("全部完成")Output
worker 2 完成
worker 1 完成
worker 3 完成
全部完成go f() hands the function to the scheduler without waiting. When main exits, other goroutines are killed — hence WaitGroup or a channel to synchronise.
channel
Go
// 无缓冲:发送和接收必须同时就绪
ch := make(chan int)
go func() { ch <- 42 }()
fmt.Println(<-ch)
// 有缓冲:缓冲区没满就能发
buf := make(chan string, 2)
buf <- "a"
buf <- "b"
fmt.Println(<-buf, <-buf)
// 关闭与 range
nums := make(chan int, 3)
nums <- 1; nums <- 2; nums <- 3
close(nums)
for v := range nums {
fmt.Print(v, " ")
}
fmt.Println()Output
42
a b
1 2 3 | Kind | Behaviour |
|---|---|
无缓冲 make(chan T) | 发送阻塞直到有人接收 |
有缓冲 make(chan T, n) | 缓冲满才阻塞 |
只读 <-chan T | 只能接收 |
只写 chan<- T | 只能发送 |
select 多路复用
Go
a := make(chan string)
b := make(chan string)
go func() { time.Sleep(50 * time.Millisecond); a <- "来自 a" }()
go func() { time.Sleep(100 * time.Millisecond); b <- "来自 b" }()
for i := 0; i < 2; i++ {
select {
case v := <-a:
fmt.Println(v)
case v := <-b:
fmt.Println(v)
case <-time.After(time.Second):
fmt.Println("超时")
}
}Output
来自 a
来自 bMutex
Go
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.n
}
var c Counter
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() { defer wg.Done(); c.Inc() }()
}
wg.Wait()
fmt.Println(c.Get())Output
1000Channels suit "passing data and ownership"; mutexes suit "protecting shared state". Counters, caches and pools are simpler with a mutex; pipelines and task distribution want channels.
竞态检测
Bash
go run -race main.go
go test -race ./...Output
==================
WARNING: DATA RACE
Write at 0x00c00001c088 by goroutine 7:
main.main.func1()
/tmp/main.go:9 +0x44
Previous read at 0x00c00001c088 by main goroutine:
main.main()
/tmp/main.go:12 +0x8c
==================常见模式
Go
// 1. 工作池:固定 worker 消费任务
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 0; w < 3; w++ {
go func() {
for j := range jobs {
results <- j * j
}
}()
}
// 2. 限流:令牌桶
limiter := time.Tick(100 * time.Millisecond)
for req := range requests {
<-limiter
go handle(req)
}
// 3. 取消:context
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-ctx.Done():
fmt.Println("已取消")
case v := <-ch:
fmt.Println(v)
}context.Context is the standard way to carry cancellation and deadlines — always the first parameter. Never store it in a struct or pass nil; use context.TODO() when unsure.goroutine
Go
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("worker %d 完成\n", id)
}
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("全部完成")Output
worker 2 完成
worker 1 完成
worker 3 完成
全部完成go f() 只是把函数交给调度器,不等它结束。主 goroutine 退出时其他 goroutine 全部被杀,所以必须有 WaitGroup 或 channel 做同步。
channel
Go
// 无缓冲:发送和接收必须同时就绪
ch := make(chan int)
go func() { ch <- 42 }()
fmt.Println(<-ch)
// 有缓冲:缓冲区没满就能发
buf := make(chan string, 2)
buf <- "a"
buf <- "b"
fmt.Println(<-buf, <-buf)
// 关闭与 range
nums := make(chan int, 3)
nums <- 1; nums <- 2; nums <- 3
close(nums)
for v := range nums {
fmt.Print(v, " ")
}
fmt.Println()Output
42
a b
1 2 3 | 类型 | 行为 |
|---|---|
无缓冲 make(chan T) | 发送阻塞直到有人接收 |
有缓冲 make(chan T, n) | 缓冲满才阻塞 |
只读 <-chan T | 只能接收 |
只写 chan<- T | 只能发送 |
select 多路复用
Go
a := make(chan string)
b := make(chan string)
go func() { time.Sleep(50 * time.Millisecond); a <- "来自 a" }()
go func() { time.Sleep(100 * time.Millisecond); b <- "来自 b" }()
for i := 0; i < 2; i++ {
select {
case v := <-a:
fmt.Println(v)
case v := <-b:
fmt.Println(v)
case <-time.After(time.Second):
fmt.Println("超时")
}
}Output
来自 a
来自 bMutex
Go
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.n
}
var c Counter
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() { defer wg.Done(); c.Inc() }()
}
wg.Wait()
fmt.Println(c.Get())Output
1000channel 适合「传递数据与所有权」,Mutex 适合「保护共享状态」。计数、缓存、连接池这类用 Mutex 更简单;流水线、任务分发用 channel。
竞态检测
Bash
go run -race main.go
go test -race ./...Output
==================
WARNING: DATA RACE
Write at 0x00c00001c088 by goroutine 7:
main.main.func1()
/tmp/main.go:9 +0x44
Previous read at 0x00c00001c088 by main goroutine:
main.main()
/tmp/main.go:12 +0x8c
==================常见模式
Go
// 1. 工作池:固定 worker 消费任务
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 0; w < 3; w++ {
go func() {
for j := range jobs {
results <- j * j
}
}()
}
// 2. 限流:令牌桶
limiter := time.Tick(100 * time.Millisecond)
for req := range requests {
<-limiter
go handle(req)
}
// 3. 取消:context
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-ctx.Done():
fmt.Println("已取消")
case v := <-ch:
fmt.Println(v)
}context.Context 是 Go 并发里传递取消信号与超时的标准方式,永远作为第一个参数传下去。不要把它存进结构体,也不要传 nil——不确定就用 context.TODO()。