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 检查竞争。

笔记加载中…