Go 语言的并发模型基于 CSP(Communicating Sequential Processes)理论,核心思想是:
Don’t communicate by sharing memory; share memory by communicating.
Goroutine
Goroutine 是 Go 运行时管理的轻量级线程,创建成本极低(初始栈约 2KB)。
package main
import (
"fmt"
"time"
)
func sayHello(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
go sayHello("camelia") // 启动一个 goroutine
go sayHello("world")
time.Sleep(100 * time.Millisecond) // 等待 goroutine 执行
}
WaitGroup 同步
用 sync.WaitGroup 比 time.Sleep 更可靠:
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d 开始工作\n", id)
// 模拟工作...
fmt.Printf("Worker %d 完成\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait() // 等待所有 worker 完成
fmt.Println("所有工作完成!")
}
Channel
Channel 是 goroutine 之间通信的管道,类型安全,支持阻塞。
无缓冲 Channel
ch := make(chan int) // 无缓冲:发送方阻塞直到接收方准备好
go func() {
ch <- 42 // 发送(阻塞直到有人接收)
}()
val := <-ch // 接收(阻塞直到有人发送)
fmt.Println(val) // 42
有缓冲 Channel
ch := make(chan string, 3) // 缓冲大小为 3
ch <- "first" // 不阻塞
ch <- "second" // 不阻塞
ch <- "third" // 不阻塞
// ch <- "fourth" // 阻塞!缓冲已满
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"
Range 遍历 Channel
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 必须关闭,否则 range 无法结束
}
func main() {
ch := make(chan int, 5)
go producer(ch)
for val := range ch { // 自动检测 channel 关闭
fmt.Println(val)
}
}
Select 多路复用
select 用于同时等待多个 channel,类似 switch 但用于通信:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { ch1 <- "来自 ch1" }()
go func() { ch2 <- "来自 ch2" }()
for i := 0; i < 2; i++ {
select {
case msg := <-ch1:
fmt.Println("收到:", msg)
case msg := <-ch2:
fmt.Println("收到:", msg)
case <-time.After(1 * time.Second):
fmt.Println("超时!")
}
}
}
实战:工作池(Worker Pool)
package main
import (
"fmt"
"sync"
)
func workerPool(numWorkers, numJobs int) {
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// 启动 workers
var wg sync.WaitGroup
for w := 0; w < numWorkers; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- job * job // 计算平方
}
}()
}
// 发送任务
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 等待所有 worker 完成后关闭结果 channel
go func() {
wg.Wait()
close(results)
}()
// 收集结果
for result := range results {
fmt.Println(result)
}
}
func main() {
workerPool(3, 10) // 3 个 worker,处理 10 个任务
}
常见陷阱
Goroutine 泄漏
// ❌ 错误:没有退出机制,goroutine 永远阻塞
func leak() {
ch := make(chan int)
go func() {
val := <-ch // 没人发送,永远阻塞
fmt.Println(val)
}()
}
// ✅ 正确:使用 context 控制生命周期
func noLeak(ctx context.Context) {
ch := make(chan int)
go func() {
select {
case val := <-ch:
fmt.Println(val)
case <-ctx.Done(): // 收到取消信号,退出
return
}
}()
}
Go 的并发模型通过 goroutine + channel 让并发编程变得直观而安全喵~