Go语言-初学者日记(六) 并发编程
Go 语言内置并发模型,被誉为“并发界的扛把子”。它的并发不是线程、不是回调地狱,而是轻量、优雅的 goroutine + channel 模式。
这一篇带你一步步掌握 Go 并发编程:从 goroutine 到 channel、select、context、sync,写出高并发高性能代码不是梦!
🧵 一、开启并发的第一步:goroutine
func sayHello() {
fmt.Println("Hello from goroutine")
}
go sayHello()
fmt.Println("Main finished")
📌 使用 go
关键字创建 goroutine。它是 Go 的轻量线程:
- 每个 goroutine 栈空间初始仅 2KB,远小于线程
- Go runtime 自动调度,使用 M:N 调度模型(goroutine 与系统线程动态映射)
⚠️ 注意:如果 main()
提前退出,goroutine 没机会执行。
time.Sleep(time.Second)
📬 二、goroutine 通信神器:channel
ch := make(chan string)
go func() {
ch <- "数据来啦!"
}()
msg := <-ch
fmt.Println(msg)
✅ channel 特点:
- 类型安全
- 无缓冲时:发送和接收会互相阻塞(同步通信)
- channel 是“通信的内存”,不是“共享的内存”
🔁 三、带缓冲的 channel
ch := make(chan int, 2) // 缓冲区大小 2
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
🧠 使用场景:
- 任务管道
- 控制速率(限流)
- 缓冲写入日志、数据等
🔀 四、select:监听多个 channel
select {
case msg1 := <-ch1:
fmt.Println("收到1:", msg1)
case msg2 := <-ch2:
fmt.Println("收到2:", msg2)
default:
fmt.Println("啥也没收到")
}
- 类似
switch
,但用于 channel - 可以实现超时、轮询、优先级控制等逻辑
📌 配合 time.After()
做超时控制:
select {
case data := <-ch:
fmt.Println("收到数据", data)
case <-time.After(1 * time.Second):
fmt.Println("超时啦")
}
⏳ 五、context:优雅地取消 goroutine
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("收到取消信号,退出")
return
default:
fmt.Println("执行中...")
time.Sleep(500 * time.Millisecond)
}
}
}(ctx)
time.Sleep(2 * time.Second)
cancel()
📌 常见使用场景:
- HTTP 请求取消
- 数据库操作超时
- 子任务取消控制
函数:
名称 | 用途 |
---|---|
WithCancel | 手动调用 cancel() |
WithTimeout | 一段时间后自动取消 |
WithDeadline | 指定时间点取消 |
🧱 六、sync 包:共享数据的并发控制
✅ WaitGroup:等待一组 goroutine 完成
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println("任务", i)
}(i)
}
wg.Wait()
✅ Mutex:互斥锁,保护共享资源
var mu sync.Mutex
var count int
for i := 0; i < 10; i++ {
go func() {
mu.Lock()
count++
mu.Unlock()
}()
}
✅ sync.Once:只执行一次(懒加载)
var once sync.Once
once.Do(func() {
fmt.Println("只执行一次")
})
⚙️ 七、channel 关闭与判断
close(ch)
val, ok := <-ch
if !ok {
fmt.Println("通道已关闭")
}
- 关闭后不能再写入,否则 panic
- 读已关闭通道会返回零值和 false
🧠 八、小结与对比
技术点 | 用途 |
---|---|
goroutine | 创建并发执行单元 |
channel | 安全通信管道 |
select | 多路 channel 选择 |
context | 协程生命周期控制 |
sync | 互斥、等待、只执行一次等 |
💡 实战推荐练习
- ✅ 写一个并发爬虫,使用 goroutine + channel 控制并发数量
- ✅ 用 context 控制下载任务的取消
- ✅ 构建一个日志服务,带缓冲 channel + goroutine 写入日志文件
- ✅ 使用 WaitGroup 模拟并发任务同步收集结果
🏁 下一篇预告
👉 Go语言-初学者日记(七):项目实战篇:用 Go 写一个 RESTful API 服务!