Go 并发

goroutine、channel、select、WaitGroup、Mutex、以及竞态检测。

Advanced
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 
KindBehaviour
无缓冲 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
来自 b

Mutex

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
1000
Channels 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.

Friends & Partners