并发编程
Go 的并发模型基于 CSP(Communicating Sequential Processes):goroutine 之间通过 channel 传递消息,而不是共享内存。口号是:
Don't communicate by sharing memory; share memory by communicating.
1. goroutine
go 关键字启动一个 goroutine——比线程轻量得多(初始栈 2KB)。
package main
import (
"fmt"
"time"
)
func say(msg string) {
for i := 0; i < 3; i++ {
fmt.Println(msg, i)
time.Sleep(10 * time.Millisecond)
}
}
func main() {
go say("goroutine")
say("main") // 主 goroutine
// 等待 50ms 让另一个 goroutine 完成
time.Sleep(50 * time.Millisecond)
fmt.Println("结束")
}⚠️主 goroutine 退出 = 整个程序退出
main() 返回时,所有其他 goroutine 立刻终止。需要用 sync.WaitGroup 或 channel 等待。
2. channel
channel 是 goroutine 之间的类型化管道。<- 方向:ch <- v 发送,v := <-ch 接收。
package main
import "fmt"
func main() {
// 无缓冲 channel:发送和接收必须同时就绪
ch := make(chan int)
go func() {
ch <- 42 // 发送
}()
v := <-ch // 接收(会阻塞直到另一方发送)
fmt.Println("收到:", v)
// 有缓冲 channel:容量满前发送不阻塞
buf := make(chan string, 2)
buf <- "a"
buf <- "b"
// buf <- "c" // 这里会阻塞
fmt.Println(<-buf, <-buf)
}单向 channel
为 API 表达"只能发"或"只能收"的约束。
package main
import (
"fmt"
"time"
)
// 只能收
func consumer(in <-chan int) {
for v := range in {
fmt.Println("消费:", v)
}
}
// 只能发
func producer(out chan<- int) {
for i := 1; i <= 3; i++ {
out <- i
}
close(out)
}
func main() {
ch := make(chan int, 3)
go producer(ch)
consumer(ch)
time.Sleep(10 * time.Millisecond)
}💡close 的作用
- 发送方
close(ch)表示"不会再发了" - 接收方
v, ok := <-ch,ok==false表示 channel 已关闭且无数据 - 用
for v := range ch自动退出 - 关闭已关闭的 channel 会 panic;接收方不要关闭
3. select
select 让 goroutine 等待多个 channel 操作。
package main
import (
"fmt"
"time"
)
func main() {
a := make(chan string)
b := make(chan string)
go func() { time.Sleep(20 * time.Millisecond); a <- "A" }()
go func() { time.Sleep(10 * time.Millisecond); b <- "B" }()
for i := 0; i < 2; i++ {
select {
case msg := <-a:
fmt.Println("收到 a:", msg)
case msg := <-b:
fmt.Println("收到 b:", msg)
}
}
}超时模式
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string, 1)
go func() {
time.Sleep(100 * time.Millisecond)
ch <- "结果"
}()
select {
case res := <-ch:
fmt.Println(res)
case <-time.After(30 * time.Millisecond):
fmt.Println("超时!")
}
}4. sync 包:WaitGroup 和 Mutex
WaitGroup:等待一组 goroutine
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 通知完成
time.Sleep(10 * time.Millisecond)
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() // 阻塞直到计数为 0
fmt.Println("全部完成")
}ℹ️输出顺序是随机的
goroutine 由调度器决定先后,所以完成顺序不固定。
Mutex:互斥锁
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.n
}
func main() {
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.Value()) // 1000
}⚠️race 警告
没有 Lock/Unlock 时,并发写会丢失更新。运行 go run -race main.go 可检测数据竞争。
5. Worker Pool 模式
固定数量的 worker 反复从任务 channel 取任务。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
time.Sleep(5 * time.Millisecond) // 模拟耗时
results <- j * 2
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
// 3 个 worker
var wg sync.WaitGroup
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// 9 个任务
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// 等 worker 全部退出再关结果 channel
go func() { wg.Wait(); close(results) }()
sum := 0
for r := range results {
sum += r
}
fmt.Println("总和:", sum) // 1+2+...+9 × 2 = 90
}6. 并发安全的"任务并发执行器"
package main
import (
"fmt"
"sync"
"time"
)
// 多个 goroutine 产生数据,合并到一个 channel
func fanIn(chans ...<-chan string) <-chan string {
out := make(chan string)
var wg sync.WaitGroup
for _, c := range chans {
wg.Add(1)
go func(c <-chan string) {
defer wg.Done()
for v := range c {
out <- v
}
}(c)
}
go func() { wg.Wait(); close(out) }()
return out
}
func produce(name string, n int) <-chan string {
c := make(chan string)
go func() {
defer close(c)
for i := 0; i < n; i++ {
time.Sleep(5 * time.Millisecond)
c <- fmt.Sprintf("%s-%d", name, i)
}
}()
return c
}
func main() {
out := fanIn(produce("A", 3), produce("B", 3), produce("C", 3))
for msg := range out {
fmt.Println(msg)
}
}🎯 练习
// 任务:把 [1, 2, 3, ..., 100] 分给 4 个 worker 求和,最后合并
// 1. 用 jobs channel 派发任务(每个 worker 拿到一段区间)
// 2. 用 results channel 收集部分和
// 3. 主 goroutine 求总和并打印
// 期望结果:5050
package main
import (
"fmt"
"sync"
)
func main() {
const workers = 4
const total = 100
jobs := make(chan [2]int, workers)
results := make(chan int, workers)
var wg sync.WaitGroup
for w := 0; w < workers; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for r := range jobs {
s := 0
for i := r[0]; i <= r[1]; i++ {
s += i
}
results <- s
}
}()
}
// 划分区间
step := total / workers
for i := 0; i < workers; i++ {
lo := i*step + 1
hi := (i + 1) * step
if i == workers-1 {
hi = total
}
jobs <- [2]int{lo, hi}
}
close(jobs)
go func() { wg.Wait(); close(results) }()
sum := 0
for s := range results {
sum += s
}
fmt.Println("sum =", sum)
}小结
- ✅
go启动 goroutine(轻量线程) - ✅ channel:
make(chan T)无缓冲,make(chan T, n)有缓冲 - ✅ 发送方负责
close(ch),接收方用for v := range ch或ok检测 - ✅
select多路复用 +time.After超时 - ✅
sync.WaitGroup等一组 goroutine - ✅
sync.Mutex保护共享状态;用go run -race检测 - ✅ Worker Pool 模式:固定 N 个 worker 处理无限任务流
- ✅ Fan-in 模式:多个 channel 合并到一个
下一章 泛型。