第一章:Go 并发模型 ------ CSP 与 Goroutine
1.1 传统并发 vs Go 并发
| 模型 | 代表语言 | 核心机制 | 问题 |
|---|
- 共享内存 | Java/C++ | 线程 + 锁(Mutex) | 死锁、竞态、调试困难
- 消息传递 | Erlang | 进程 + 消息邮箱 | 进程创建开销大
- CSP(通信顺序进程) | Go | Goroutine + Channel | 学习曲线(需思维转换)
Go 的信条 :"Don't communicate by sharing memory; share memory by communicating. "
(不要通过共享内存来通信,而应通过通信来共享内存。)
1.2 Goroutine 是什么?
-
不是 OS 线程 !而是由 Go 运行时(Runtime)管理的用户态轻量级线程
-
特点 :
- 初始栈仅 2KB(可动态扩容至几 MB)
- 创建成本极低(约 200ns,对比线程 1--10μs)
- 由 M:N 调度器管理(M 个 OS 线程调度 N 个 Goroutine)
-
启动方式 :在函数前加
go关键字go sayHello("Alice") // 启动新 goroutine
注意 :Goroutine 没有返回值,也不抛出 panic 到父 goroutine(除非主 goroutine panic)。
第二章:Channel ------ Goroutine 间的安全通道
2.1 Channel 基础
-
类型 :
chan T(有缓冲)或chan<- T/<-chan T(单向) -
操作 :
- 发送:
ch <- value - 接收:
value := <-ch或value, ok := <-ch
- 发送:
-
阻塞行为 :
- 无缓冲 channel:发送和接收必须同时就绪,否则阻塞
- 有缓冲 channel:缓冲满时发送阻塞,空时接收阻塞
// 无缓冲 channel(同步)
ch := make(chan string)
go func() {
ch <- "hello" // 阻塞,直到有人接收
}()
msg := <-ch // 接收,解除阻塞
2.2 Channel 作为函数参数(类型安全)
// 生产者:只发送
func producer(out chan<- string) {
out <- "data"
close(out) // 关闭通道,通知消费者结束
}
// 消费者:只接收
func consumer(in <-chan string) {
for msg := range in { // 自动检测 close
fmt.Println(msg)
}
}
优势:编译器强制方向,防止误用。
第三章:五大并发模式实战
3.1 模式一:Worker Pool(限制并发数)
场景:避免同时发起 10,000 个 HTTP 请求压垮下游。
// internal/concurrent/workerpool.go
package concurrent
func WorkerPool(tasks []Task, maxWorkers int) []Result {
taskChan := make(chan Task, len(tasks))
resultChan := make(chan Result, len(tasks))
// 启动固定数量 worker
for i := 0; i < maxWorkers; i++ {
go func() {
for task := range taskChan {
result := process(task) // 执行任务
resultChan <- result
}
}()
}
// 发送所有任务
for _, task := range tasks {
taskChan <- task
}
close(taskChan) // 通知 workers 结束
// 收集结果
var results []Result
for i := 0; i < len(tasks); i++ {
results = append(results, <-resultChan)
}
return results
}
关键:
maxWorkers控制资源消耗- 通道关闭后,
range自动退出
3.2 模式二:Pipeline(流式处理)
场景:数据清洗 → 转换 → 存储,各阶段并行。
// 阶段1:生成数据
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
// 阶段2:平方
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}
// 使用
for n := range sq(gen(2, 3, 4)) {
fmt.Println(n) // 4, 9, 16
}
优势:各阶段解耦,天然支持背压(backpressure)。
3.3 模式三:Fan-out / Fan-in(广播与聚合)
场景:并行调用多个服务,聚合结果。
// Fan-out:广播任务到多个 worker
func fanOut(in <-chan Task, numWorkers int) []<-chan Result {
outs := make([]<-chan Result, numWorkers)
for i := 0; i < numWorkers; i++ {
outs[i] = worker(in)
}
return outs
}
// Fan-in:合并多个通道
func fanIn(channels ...<-chan Result) <-chan Result {
out := make(chan Result)
for _, ch := range channels {
go func(c <-chan Result) {
for r := range c {
out <- r
}
}(ch)
}
return out
}
注意 :Fan-in 需额外机制确保所有 worker 完成后关闭
out(见 3.5)。
3.4 模式四:Select 多路复用
场景:超时控制、默认分支、多通道监听。
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
return
default:
fmt.Println("No message ready")
// 非阻塞
}
用途:
- 实现超时
- 避免阻塞(default 分支)
- 同时监听多个事件源
3.5 模式五:Context 传播(优雅取消)
场景:HTTP 请求取消时,终止所有后台 goroutine。
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 主 goroutine 结束时取消
// 启动 worker
go func(ctx context.Context) {
for {
select {
case <-ctx.Done(): // 检测取消信号
fmt.Println("Worker canceled:", ctx.Err())
return
default:
// do work
time.Sleep(100 * time.Millisecond)
}
}
}(ctx)
// 模拟请求取消
time.Sleep(500 * time.Millisecond)
cancel() // 发送取消信号
Context 规则:
- 永远从请求中获取 ctx (如
r.Context())- 向下传递 ctx,不要存储在 struct 中
- 不要传递 nil ctx
第四章:实战项目 ------ 高并发 API 网关
我们将构建一个 /api/profile 接口,它并行调用三个下游服务:
- 用户服务 :
GET /users/{id}→ 返回姓名、邮箱 - 订单服务 :
GET /orders?user_id={id}→ 返回最近订单 - 库存服务 :
GET /inventory/user/{id}→ 返回可用积分
要求:
- 任一服务失败 → 整体返回 500
- 支持请求级超时(如 1 秒)
- 防止 Goroutine 泄漏
- 可观测(日志记录各服务耗时)
4.1 项目结构扩展
my-gateway/
├── cmd/
│ └── my-gateway/
│ └── main.go
├── internal/
│ ├── handler/ # HTTP 处理器
│ ├── service/ # 聚合服务
│ │ └── profile.go # 核心并发逻辑
│ ├── client/ # 下游服务客户端
│ │ ├── user.go
│ │ ├── order.go
│ │ └── inventory.go
│ └── config/
└── ...
4.2 定义数据模型
// internal/service/profile.go
type UserProfile struct {
UserID string `json:"user_id"`
Name string `json:"name"`
Email string `json:"email"`
LastOrder *Order `json:"last_order,omitempty"`
Points int `json:"points"`
}
type Order struct {
ID string `json:"id"`
Amount int `json:"amount"`
}
4.3 实现下游客户端(模拟)
// internal/client/user.go
func GetUser(ctx context.Context, userID string) (*User, error) {
// 模拟网络延迟
time.Sleep(100 * time.Millisecond)
// 模拟错误
if userID == "error" {
return nil, errors.New("user not found")
}
return &User{ID: userID, Name: "Alice", Email: "alice@example.com"}, nil
}
关键 :所有客户端方法接收 ctx,以便支持取消/超时。
4.4 核心:并发聚合服务
// internal/service/profile.go
func GetProfile(ctx context.Context, userID string) (*UserProfile, error) {
// 1. 创建子 context(带超时)
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel() // 确保资源释放
// 2. 启动三个 goroutine 并行调用
type result struct {
user *User
orders []Order
points int
err error
}
ch := make(chan result, 1) // 缓冲 1,避免 goroutine 泄漏
go func() {
user, err := client.GetUser(ctx, userID)
if err != nil {
ch <- result{err: fmt.Errorf("user: %w", err)}
return
}
orders, err := client.GetOrders(ctx, userID)
if err != nil {
ch <- result{err: fmt.Errorf("orders: %w", err)}
return
}
points, err := client.GetInventory(ctx, userID)
if err != nil {
ch <- result{err: fmt.Errorf("inventory: %w", err)}
return
}
ch <- result{user: user, orders: orders, points: points}
}()
// 3. 等待结果或超时
select {
case r := <-ch:
if r.err != nil {
return nil, r.err
}
lastOrder := (*Order)(nil)
if len(r.orders) > 0 {
lastOrder = &r.orders[0]
}
return &UserProfile{
UserID: r.user.ID,
Name: r.user.Name,
Email: r.user.Email,
LastOrder: lastOrder,
Points: r.points,
}, nil
case <-ctx.Done():
return nil, ctx.Err() // 超时或取消
}
}
设计亮点:
- 单通道聚合:避免多个通道竞争
- 错误包装 :明确错误来源(
user: not found)- defer cancel():确保 context 被清理
- 缓冲通道:即使 receiver 未 ready,sender 也不会阻塞
4.5 HTTP Handler 集成
// internal/handler/profile.go
func ProfileHandler(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
if userID == "" {
http.Error(w, "Missing user_id", http.StatusBadRequest)
return
}
profile, err := service.GetProfile(r.Context(), userID)
if err != nil {
logrus.WithError(err).Error("Failed to get profile")
if errors.Is(err, context.DeadlineExceeded) {
http.Error(w, "Request timeout", http.StatusGatewayTimeout)
} else {
http.Error(w, "Internal error", http.StatusInternalServerError)
}
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(profile)
}
关键 :使用
r.Context()传递请求上下文,使超时/取消生效。
第五章:防止 Goroutine 泄漏 ------ 常见陷阱与解决方案
5.1 泄漏场景一:未读取的通道
// ❌ 危险:goroutine 永远阻塞在 ch <-
ch := make(chan string)
go func() {
ch <- "hello"
}()
// 主 goroutine 退出,但后台 goroutine 仍在等待接收
✅ 解决方案:
- 使用缓冲通道 (
make(chan T, 1)) - 或确保有接收者
5.2 泄漏场景二:未取消的定时器
// ❌ 危险:ticker 不会自动停止
ticker := time.NewTicker(1 * time.Second)
go func() {
for range ticker.C {
// do something
}
}()
// 忘记调用 ticker.Stop()
✅ 解决方案:
-
使用
context控制生命周期 -
在 defer 中停止
ctx, cancel := context.WithCancel(context.Background())
defer cancel()ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()go func() {
for {
select {
case <-ticker.C:
// work
case <-ctx.Done():
return
}
}
}()
5.3 泄漏场景三:未处理的 panic
go func() {
panic("oops") // 主 goroutine 不会 crash,但此 goroutine 消失
}()
✅ 解决方案:
-
在 goroutine 顶层加
recovergo func() {
defer func() {
if r := recover(); r != nil {
logrus.Error("Goroutine panic:", r)
}
}()
// ...
}()
注意:不要滥用 recover,仅用于守护关键后台任务。
第六章:性能分析 ------ 使用 pprof 定位瓶颈
Go 内置性能分析工具 net/http/pprof。
6.1 启用 pprof
// main.go
import _ "net/http/pprof"
func main() {
// ... 其他路由
// pprof 自动注册 /debug/pprof/*
log.Fatal(http.ListenAndServe(":6060", nil)) // 单独端口更安全
}
6.2 分析 CPU 热点
# 采集 30 秒 CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# 在交互模式中
(pprof) top10 # 显示 top 10 函数
(pprof) web # 生成调用图(需 Graphviz)
6.3 分析内存分配
go tool pprof http://localhost:6060/debug/pprof/heap
(pprof) top
实战建议:
- 在压力测试时开启 pprof
- 关注
alloc_space(总分配)而非inuse_space(当前使用)
第七章:测试并发代码
7.1 单元测试超时
func TestGetProfile_Timeout(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
_, err := service.GetProfile(ctx, "slow_user")
if !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("Expected timeout, got %v", err)
}
}
7.2 检测竞态条件(Race Detector)
go test -race ./...
重要 :Race Detector 会显著降低性能,仅用于测试,勿用于生产。
结语:并发不是特性,而是 Go 的呼吸
Goroutine、Channel、Context ------ 这三者构成了 Go 并发的"三位一体"。
掌握它们,你便拥有了构建高性能、高可靠分布式系统的基石。