1. 为什么选择 Go 做服务器开发?

🚀 性能优势

  • 编译为原生机器码,接近 C 性能
  • 内置高效 GC,停顿时间 <1ms
  • goroutine 栈初始 2KB,可承载百万并发
  • 内置 CPU profiler,易于调优

⚡ 开发效率

  • 编译速度极快(秒级)
  • 语法简洁,学习曲线平缓
  • 强大的标准库,net/http 开箱即用
  • 静态类型 + 类型推断,安全不冗余

2. 快速安装与工程结构

安装 Go

# macOS
brew install go

# 验证安装
go version  # go version go1.22.x darwin/arm64

# 设置模块代理 (国内加速)
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GONOSUMCHECK=*

标准工程结构

myservice/
├── cmd/
│   └── server/
│       └── main.go          # 程序入口
├── internal/                # 私有包 (外部无法 import)
│   ├── handler/             # HTTP 处理器
│   ├── service/             # 业务逻辑层
│   ├── repository/          # 数据访问层
│   └── model/               # 数据模型
├── pkg/                     # 可复用的公共包
│   ├── logger/
│   └── config/
├── api/                     # OpenAPI/Protobuf 定义
├── config/                  # 配置文件
├── migrations/              # 数据库迁移
├── go.mod                   # 模块依赖
├── go.sum                   # 依赖校验
└── Makefile                 # 构建脚本
# 初始化模块
mkdir myservice && cd myservice
go mod init github.com/yourname/myservice

# 添加依赖
go get github.com/gin-gonic/gin
go get gorm.io/gorm

# 整理依赖
go mod tidy

3. Go 类型系统

package main

import "fmt"

// 基础类型
var (
    name    string  = "Alice"
    age     int     = 30
    score   float64 = 98.5
    active  bool    = true
    data    []byte  = []byte{0x48, 0x65}
)

// 类型推断
host := "localhost"
port := 8080

// 结构体 (核心数据组织方式)
type User struct {
    ID        int64     `json:"id" db:"id"`
    Name      string    `json:"name" db:"name"`
    Email     string    `json:"email" db:"email"`
    Password  string    `json:"-"`              // json 序列化时忽略
    CreatedAt time.Time `json:"created_at"`
}

// 方法 (值接收者 vs 指针接收者)
func (u User) String() string {
    return fmt.Sprintf("User{%d, %s}", u.ID, u.Name)
}

func (u *User) SetEmail(email string) {
    u.Email = email  // 修改原始数据需要指针接收者
}

// 接口
type Stringer interface {
    String() string
}

// 枚举模拟
type Status int

const (
    StatusPending Status = iota  // 0
    StatusActive                 // 1
    StatusDisabled               // 2
)

func (s Status) String() string {
    return [...]string{"pending", "active", "disabled"}[s]
}

// map 与 slice
func main() {
    // slice
    users := []User{
        {ID: 1, Name: "Alice"},
        {ID: 2, Name: "Bob"},
    }
    users = append(users, User{ID: 3, Name: "Charlie"})

    // map
    cache := map[string]int{
        "hits":   100,
        "misses": 5,
    }
    // 安全访问 (comma-ok 惯用法)
    val, ok := cache["hits"]
    if ok {
        fmt.Println("命中次数:", val)
    }

    // range 遍历
    for i, u := range users {
        fmt.Printf("[%d] %s\n", i, u.Name)
    }
}

4. 错误处理

ℹ️

Go 的错误哲学

Go 将错误视为普通值,而非异常。函数通过多返回值返回错误,调用者必须显式处理——这使错误处理可见且可靠。

package main

import (
    "errors"
    "fmt"
)

// 自定义错误类型
type AppError struct {
    Code    int
    Message string
    Err     error // 原始错误 (错误链)
}

func (e *AppError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

func (e *AppError) Unwrap() error { return e.Err }

// 哨兵错误
var (
    ErrNotFound   = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrValidation = errors.New("validation failed")
)

func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, &AppError{Code: 400, Message: "invalid id", Err: ErrValidation}
    }
    if id > 100 {
        return nil, fmt.Errorf("findUser: %w", ErrNotFound) // %w 包装错误
    }
    return &User{ID: int64(id), Name: "Alice"}, nil
}

func main() {
    user, err := findUser(-1)
    if err != nil {
        // errors.Is: 检查错误链中是否有目标错误
        if errors.Is(err, ErrValidation) {
            fmt.Println("参数校验失败:", err)
        }

        // errors.As: 从错误链中提取特定类型
        var appErr *AppError
        if errors.As(err, &appErr) {
            fmt.Printf("应用错误码: %d\n", appErr.Code)
        }
        return
    }
    fmt.Println(user)
}

5. Goroutine 并发模型

goroutine 是 Go 的核心竞争力。一个 goroutine 只需约 2KB 内存,Go 运行时用 M:N 调度模型将数百万 goroutine 映射到操作系统线程上。

G1 (运行中)
G2 (运行中)
G3 (等待IO)
G4 (就绪)
G5 (等待Channel)
G6 (就绪)
↑ Go 运行时调度器将 goroutine 分配到 OS 线程 (P/M) 上执行
package main

import (
    "fmt"
    "sync"
    "time"
)

// ─── 基础 goroutine ───────────────────────────────────────────
func basicGoroutine() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {  // 注意:传入参数避免闭包变量捕获问题
            defer wg.Done()
            time.Sleep(time.Duration(id*100) * time.Millisecond)
            fmt.Printf("goroutine %d 完成\n", id)
        }(i)
    }
    wg.Wait()
    fmt.Println("所有 goroutine 完成")
}

// ─── Channel 通信 ─────────────────────────────────────────────
func channelDemo() {
    // 无缓冲 channel: 发送者阻塞直到接收者准备好
    ch := make(chan int)
    // 有缓冲 channel: 发送不超过容量时不阻塞
    buffered := make(chan int, 10)

    // 生产者-消费者模式
    go func() {
        for i := 1; i <= 5; i++ {
            buffered <- i  // 发送
        }
        close(buffered)    // 关闭 channel,接收者会收到零值
    }()

    for v := range buffered {  // range channel: 直到关闭
        fmt.Printf("收到: %d\n", v)
    }

    _ = ch
}

// ─── Select 多路复用 ──────────────────────────────────────────
func selectDemo() {
    ch1 := make(chan string, 1)
    ch2 := make(chan string, 1)
    timeout := time.After(2 * time.Second)

    go func() {
        time.Sleep(300 * time.Millisecond)
        ch1 <- "来自 ch1"
    }()
    go func() {
        time.Sleep(500 * time.Millisecond)
        ch2 <- "来自 ch2"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println("ch1:", msg)
        case msg := <-ch2:
            fmt.Println("ch2:", msg)
        case <-timeout:
            fmt.Println("超时!")
            return
        }
    }
}

// ─── Context 控制取消 ─────────────────────────────────────────
func contextDemo() {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    result := make(chan string, 1)
    go func() {
        // 模拟耗时操作
        select {
        case <-time.After(2 * time.Second): // 2秒后完成
            result <- "操作完成"
        case <-ctx.Done(): // 超时或取消
            fmt.Println("操作被取消:", ctx.Err())
        }
    }()

    select {
    case r := <-result:
        fmt.Println(r)
    case <-ctx.Done():
        fmt.Println("主协程超时")
    }
}

func main() {
    basicGoroutine()
    channelDemo()
    selectDemo()
}

6. 并发安全:Mutex 与 sync 包

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// ─── 互斥锁 ───────────────────────────────────────────────────
type SafeCounter struct {
    mu sync.RWMutex
    v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()         // 写锁
    defer c.mu.Unlock()
    c.v[key]++
}

func (c *SafeCounter) Value(key string) int {
    c.mu.RLock()        // 读锁 (允许并发读)
    defer c.mu.RUnlock()
    return c.v[key]
}

// ─── atomic 原子操作 (比 Mutex 更快) ─────────────────────────
var requestCount atomic.Int64

func handleRequest() {
    requestCount.Add(1)
}

// ─── sync.Once 单例初始化 ──────────────────────────────────────
var (
    instance *Database
    once     sync.Once
)

func GetDB() *Database {
    once.Do(func() {  // 保证只执行一次,即使并发调用
        instance = &Database{conn: "initialized"}
        fmt.Println("数据库连接初始化")
    })
    return instance
}

// ─── sync.Pool 对象复用 ───────────────────────────────────────
var bufferPool = sync.Pool{
    New: func() any {
        return make([]byte, 0, 4096) // 初始 4KB buffer
    },
}

func processRequest(data []byte) {
    buf := bufferPool.Get().([]byte) // 从 pool 取
    defer func() {
        buf = buf[:0]           // 清空但保留容量
        bufferPool.Put(buf)    // 还回 pool
    }()
    buf = append(buf, data...)
    // 处理 buf...
}

type Database struct{ conn string }

func main() {
    counter := &SafeCounter{v: make(map[string]int)}
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Inc("requests")
        }()
    }
    wg.Wait()
    fmt.Println("最终计数:", counter.Value("requests")) // 100
}

7. 接口与泛型

package main

import "fmt"

// ─── 接口:Go 的多态核心 ──────────────────────────────────────
type Cache interface {
    Get(key string) (any, bool)
    Set(key string, value any)
    Delete(key string)
}

// 内存缓存实现
type MemCache struct {
    data map[string]any
    mu   sync.RWMutex
}

func (m *MemCache) Get(key string) (any, bool) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    v, ok := m.data[key]
    return v, ok
}

func (m *MemCache) Set(key string, value any) {
    m.mu.Lock()
    defer m.mu.Unlock()
    m.data[key] = value
}

func (m *MemCache) Delete(key string) {
    m.mu.Lock()
    defer m.mu.Unlock()
    delete(m.data, key)
}

// 使用接口,不依赖具体实现 (依赖倒置)
type UserService struct {
    cache Cache  // 可以换成 RedisCache, MemCache...
}

// ─── 泛型 (Go 1.18+) ──────────────────────────────────────────
// 泛型函数
func Map[T, U any](s []T, fn func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](s []T, fn func(T) bool) []T {
    var result []T
    for _, v := range s {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

// 泛型类型约束
type Number interface {
    ~int | ~int64 | ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// 泛型结构体:类型安全的结果类型
type Result[T any] struct {
    Data  T
    Error error
}

func (r Result[T]) Unwrap() T {
    if r.Error != nil {
        panic(r.Error)
    }
    return r.Data
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    doubled := Map(nums, func(n int) int { return n * 2 })
    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    total := Sum(nums)

    fmt.Println("doubled:", doubled)   // [2 4 6 8 10]
    fmt.Println("evens:", evens)       // [2 4]
    fmt.Println("sum:", total)         // 15
}

8. 构建完整 HTTP 服务

整合以上所有知识,构建一个带有中间件、优雅关机、配置管理的完整 HTTP 服务。

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log/slog"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

// ─── 配置 ─────────────────────────────────────────────────────
type Config struct {
    Port         string
    ReadTimeout  time.Duration
    WriteTimeout time.Duration
    IdleTimeout  time.Duration
}

func defaultConfig() Config {
    return Config{
        Port:         getEnv("PORT", "8080"),
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  30 * time.Second,
    }
}

func getEnv(key, defaultVal string) string {
    if v := os.Getenv(key); v != "" {
        return v
    }
    return defaultVal
}

// ─── 响应助手 ─────────────────────────────────────────────────
type APIResponse struct {
    Success bool   `json:"success"`
    Data    any    `json:"data,omitempty"`
    Error   string `json:"error,omitempty"`
}

func writeJSON(w http.ResponseWriter, status int, data any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, msg string) {
    writeJSON(w, status, APIResponse{Success: false, Error: msg})
}

// ─── 中间件 ───────────────────────────────────────────────────
func loggingMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            rw := &responseWriter{ResponseWriter: w, status: 200}
            next.ServeHTTP(rw, r)
            logger.Info("request",
                "method", r.Method,
                "path", r.URL.Path,
                "status", rw.status,
                "duration", time.Since(start).String(),
                "ip", r.RemoteAddr,
            )
        })
    }
}

type responseWriter struct {
    http.ResponseWriter
    status int
}

func (rw *responseWriter) WriteHeader(status int) {
    rw.status = status
    rw.ResponseWriter.WriteHeader(status)
}

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// ─── 处理器 ───────────────────────────────────────────────────
func healthHandler(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]any{
        "status": "healthy",
        "time":   time.Now().Format(time.RFC3339),
    })
}

// ─── 主函数:优雅关机 ─────────────────────────────────────────
func main() {
    cfg := defaultConfig()
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    mux := http.NewServeMux()
    mux.HandleFunc("GET /health", healthHandler)
    mux.HandleFunc("GET /api/v1/users", func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, APIResponse{
            Success: true,
            Data:    []map[string]any{{"id": 1, "name": "Alice"}},
        })
    })

    // 组合中间件
    var handler http.Handler = mux
    handler = loggingMiddleware(logger)(handler)
    handler = corsMiddleware(handler)

    server := &http.Server{
        Addr:         ":" + cfg.Port,
        Handler:      handler,
        ReadTimeout:  cfg.ReadTimeout,
        WriteTimeout: cfg.WriteTimeout,
        IdleTimeout:  cfg.IdleTimeout,
    }

    // 在 goroutine 中启动服务
    go func() {
        logger.Info("服务器启动", "port", cfg.Port)
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            logger.Error("服务器错误", "err", err)
            os.Exit(1)
        }
    }()

    // 等待中断信号 (Ctrl+C 或 kill)
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    // 优雅关机:等待进行中的请求完成 (最多30秒)
    logger.Info("正在关机...")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if err := server.Shutdown(ctx); err != nil {
        logger.Error("强制关机", "err", err)
    }
    logger.Info("服务器已关闭")
}
🎯

Go 最佳实践速查

① 永远处理错误,不用 _ 丢弃  ② 接口应当小,单一职责  ③ 不要过早优化并发  ④ 用 context 传递超时/取消  ⑤ 日志用结构化(slog)

9. Go 1.22 / 1.23 / 1.24 新特性深度解析

Go 的发布节奏非常稳定:每年两次(2 月和 8 月)。Go 1.22、1.23、1.24 带来了几个改变开发范式的重要特性,生产代码中需要重点关注。

range over func — 惰性迭代器

原理:编译器将 for range f 转换为 f(yield) 调用,其中 yield 是编译器生成的回调函数。当 yield 返回 false(对应 break)时,迭代停止。这与 Python 的 generator / Rust 的 Iterator 本质相同,但通过语法糖隐藏了复杂性。

package main

import (
    "fmt"
    "iter"   // Go 1.23 新增标准包
    "slices"
)

// ─── 定义一个惰性整数范围序列 ──────────────────────────────────
// iter.Seq[int] 等价于 func(yield func(int) bool)
// yield 返回 false 表示调用方执行了 break
func Range(start, stop, step int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := start; i < stop; i += step {
            if !yield(i) {  // 调用方 break 时停止
                return
            }
        }
    }
}

// ─── 惰性过滤器(函数式管道的基础) ──────────────────────────
func Filter[V any](seq iter.Seq[V], pred func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {          // range 直接消费迭代器
            if pred(v) && !yield(v) { // 满足条件才传出
                return
            }
        }
    }
}

// ─── 惰性映射 ─────────────────────────────────────────────────
func Map[V, W any](seq iter.Seq[V], f func(V) W) iter.Seq[W] {
    return func(yield func(W) bool) {
        for v := range seq {
            if !yield(f(v)) {
                return
            }
        }
    }
}

// ─── 带索引的双值迭代器 ───────────────────────────────────────
// iter.Seq2[K,V] 等价于 func(yield func(K, V) bool)
func Enumerate[V any](seq iter.Seq[V]) iter.Seq2[int, V] {
    return func(yield func(int, V) bool) {
        i := 0
        for v := range seq {
            if !yield(i, v) {
                return
            }
            i++
        }
    }
}

func main() {
    // 链式管道:[0,20) 中的偶数,乘以3,只取前5个
    evens := Filter(Range(0, 20, 1), func(n int) bool { return n%2 == 0 })
    tripled := Map(evens, func(n int) int { return n * 3 })

    for i, v := range Enumerate(tripled) {
        if i >= 5 { break }          // 惰性:只计算5个,剩余不计算
        fmt.Printf("index=%d, value=%d\n", i, v)
    }
    // 输出: 0,6  1,12  2,18  3,24  4,30

    // slices 包也支持迭代器
    collected := slices.Collect(Range(1, 6, 1)) // [1,2,3,4,5]
    fmt.Println(collected)
}
⚠️

常见误区:range over func 不是协程

range over func 是同步、单线程的。yield 的调用是普通函数调用,不涉及 goroutine 或 channel。如果需要真正的异步生产/消费,仍然需要 goroutine + channel。不要把它和 Python asyncio 的 async generator 混淆。

Go 1.22 ServeMux 路由增强

原理:新版 ServeMux 在匹配时先检查方法前缀(METHOD /path),再按最长前缀匹配路径。通配符参数 {name} 通过 r.PathValue("name") 提取,内部使用 trie(前缀树)加速匹配。

package main

import (
    "encoding/json"
    "net/http"
    "strconv"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

var users = map[int]User{
    1: {1, "Alice"},
    2: {2, "Bob"},
}

func main() {
    mux := http.NewServeMux()

    // ─── Go 1.22 新路由语法 ───────────────────────────────────
    // 格式:[METHOD ][HOST]/[PATH]
    // 方法限定:不匹配方法时自动返回 405 Method Not Allowed
    mux.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
        list := make([]User, 0, len(users))
        for _, u := range users {
            list = append(list, u)
        }
        json.NewEncoder(w).Encode(list)
    })

    // {id} 通配符参数:非贪婪,只匹配单段路径
    mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
        idStr := r.PathValue("id")  // Go 1.22 新增
        id, err := strconv.Atoi(idStr)
        if err != nil {
            http.Error(w, "invalid id", http.StatusBadRequest)
            return
        }
        u, ok := users[id]
        if !ok {
            http.Error(w, "not found", http.StatusNotFound)
            return
        }
        json.NewEncoder(w).Encode(u)
    })

    mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
        var u User
        if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        u.ID = len(users) + 1
        users[u.ID] = u
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(u)
    })

    mux.HandleFunc("DELETE /users/{id}", func(w http.ResponseWriter, r *http.Request) {
        id, _ := strconv.Atoi(r.PathValue("id"))
        delete(users, id)
        w.WriteHeader(http.StatusNoContent)
    })

    // {path...} 尾部通配符:贪婪匹配,包含 /
    mux.HandleFunc("GET /static/{path...}", func(w http.ResponseWriter, r *http.Request) {
        filePath := r.PathValue("path")  // 例如 "css/main.css"
        http.ServeFile(w, r, "./public/"+filePath)
    })

    http.ListenAndServe(":8080", mux)
}
⚠️

升级注意:路由优先级变化

Go 1.22 之前,/ 会匹配所有路径(catch-all)。升级后,/ 只精确匹配根路径,/{path...} 才是 catch-all。如果你的旧代码依赖 / 的 catch-all 行为,升级后需要改为 /{path...}

Go 1.22 循环变量修复

原理:Go 1.21 及之前,for i, v := range slice 中的 iv 是同一个变量在每轮迭代中被覆写。在闭包中捕获时,所有 goroutine 共享同一个变量地址,最终读到的都是最后一次迭代的值。Go 1.22 让每次迭代创建新的变量,彻底修复这个 12 年历史的陷阱。

package main

import (
    "fmt"
    "sync"
)

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    var wg sync.WaitGroup

    // ─── Go 1.21 及之前的 Bug ────────────────────────────────
    // 所有 goroutine 共享同一个 v,输出全是 5
    // for _, v := range numbers {
    //     wg.Add(1)
    //     go func() {        // 捕获 v:地址,不是值
    //         defer wg.Done()
    //         fmt.Println(v) // 全部输出 5
    //     }()
    // }

    // ─── Go 1.22+:每次迭代 v 是新变量,行为符合直觉 ────────
    for _, v := range numbers {
        wg.Add(1)
        go func() {        // v 在每次迭代都是独立变量
            defer wg.Done()
            fmt.Println(v) // 输出 1,2,3,4,5(顺序不定)
        }()
    }
    wg.Wait()

    // ─── Go 1.22 之前的正确写法(升级前需保持兼容性) ────────
    for _, v := range numbers {
        v := v  // 显式创建新变量(shadowin 技巧)
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(v)
        }()
    }
    wg.Wait()
}

GPM 协程调度器模型

原理:Go 运行时的调度器基于 GPM 模型,是理解 goroutine 性能特性的核心。与 OS 线程相比,goroutine 的创建成本约低 1000 倍(2KB 初始栈 vs 1MB),切换成本低约 10 倍(用户态上下文切换 vs 内核态切换)。

GPM 调度模型
  ┌─────────────────────────────────────────────────┐
  │              Global Run Queue (GRQ)             │  ← G 溢出时进入
  └──────────────────────┬──────────────────────────┘
                         │ 每隔 61 个 G 取一次
    ┌────────────────────┼────────────────────────┐
    │         P1         │         P2             │
    │  LRQ: [G3 G5 G7]  │  LRQ: [G1 G4]          │
    │   ↓                │   ↓                    │
    │  M1 (OS Thread)    │  M2 (OS Thread)         │
    │  正在执行 G2        │  正在执行 G6            │
    └────────────────────┴────────────────────────┘
                    Work Stealing
    P1 LRQ 空 → 从 P2 LRQ 末尾偷取 [G4]
package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    // 查看当前 GOMAXPROCS(P 的数量)
    fmt.Printf("逻辑 CPU 数: %d\n", runtime.NumCPU())
    fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))  // 0 = 只读,不修改

    // 强制设置 P 的数量(一般不需要手动设置)
    // runtime.GOMAXPROCS(4)

    // 查看当前 goroutine 数
    fmt.Printf("当前 goroutine 数: %d\n", runtime.NumGoroutine())

    // 演示 goroutine 泄漏检测:用 context 防止 goroutine 泄漏
    var wg sync.WaitGroup
    done := make(chan struct{})

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            select {
            case <-done:
                fmt.Printf("goroutine %d 正常退出\n", id)
            }
        }(i)
    }

    close(done)  // 通知所有 goroutine 退出
    wg.Wait()
    fmt.Printf("退出后 goroutine 数: %d\n", runtime.NumGoroutine())
}
⚠️

Goroutine 泄漏:最常见的 Go 内存泄漏

当 goroutine 因为 channel 永久阻塞、或等待一个永远不会触发的条件而无法退出时,就发生了 goroutine 泄漏。每个泄漏的 goroutine 至少占用 2KB 栈内存,且持有的资源(连接、文件句柄等)无法释放。使用 pprof/goroutine 端点监控 goroutine 数量,用 context.WithCanceldone channel 确保所有 goroutine 有退出路径。

slog 结构化日志(Go 1.21 正式)

原理:log/slog 是 Go 官方推出的结构化日志库,设计目标是零分配(hot path 无堆分配)、可扩展(自定义 Handler)、向后兼容(slog.Default() 输出到 log.Default())。与 zap/zerolog 等第三方库相比,slog 是标准库,无外部依赖。

package main

import (
    "context"
    "log/slog"
    "os"
    "time"
)

// ─── 自定义 Handler:添加 request_id 到所有日志 ──────────────
type RequestIDHandler struct {
    slog.Handler
}

func (h *RequestIDHandler) Handle(ctx context.Context, r slog.Record) error {
    // 从 context 提取 request_id 注入日志
    if id, ok := ctx.Value("request_id").(string); ok {
        r.AddAttrs(slog.String("request_id", id))
    }
    return h.Handler.Handle(ctx, r)
}

func main() {
    // ─── JSON 格式(生产环境)────────────────────────────────
    jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level:     slog.LevelInfo,   // 日志级别:Debug/Info/Warn/Error
        AddSource: true,             // 记录文件名和行号
        ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
            // 将 "time" 键格式化为 RFC3339
            if a.Key == slog.TimeKey {
                a.Value = slog.StringValue(a.Value.Time().Format(time.RFC3339))
            }
            return a
        },
    })

    // 包装自定义 Handler
    handler := &RequestIDHandler{Handler: jsonHandler}
    logger := slog.New(handler)
    slog.SetDefault(logger)  // 设置为全局默认 logger

    // ─── 基本用法 ─────────────────────────────────────────────
    slog.Info("服务器启动", "port", 8080, "env", "production")
    slog.Warn("请求延迟高", "path", "/api/users", "latency_ms", 450)
    slog.Error("数据库连接失败", "host", "db:5432", "err", "connection refused")

    // ─── With:预绑定字段(减少重复) ────────────────────────
    reqLogger := slog.With("request_id", "req-abc-123", "user_id", 42)
    reqLogger.Info("处理请求", "method", "GET", "path", "/profile")
    reqLogger.Info("查询用户", "query", "SELECT * FROM users WHERE id=42")

    // ─── Group:字段分组(JSON 中嵌套对象) ──────────────────
    slog.Info("HTTP 请求",
        slog.Group("request",
            "method", "POST",
            "path",   "/api/orders",
            "size",   1024,
        ),
        slog.Group("response",
            "status",  201,
            "latency", "12ms",
        ),
    )
    // 输出: {"request":{"method":"POST","path":"/api/orders","size":1024},...}

    // ─── context 传递(配合自定义 Handler) ──────────────────
    ctx := context.WithValue(context.Background(), "request_id", "req-xyz-789")
    logger.InfoContext(ctx, "用户登录", "username", "alice")
    // request_id 自动附加到日志

    // ─── 性能:使用 LogAttrs 避免反射(零分配) ──────────────
    // 当日志级别未启用时,LogAttrs 完全跳过参数求值
    logger.LogAttrs(ctx, slog.LevelDebug,
        "详细调试",
        slog.Int("goroutines", 42),
        slog.Duration("uptime", 3*time.Hour),
    )
}