🐹 Go 语言快速入门
掌握 Go 语言核心特性:类型系统、并发模型、接口设计与标准库——这些是构建高性能服务器的关键。
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 映射到操作系统线程上。
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(Go 1.22 实验 / 1.23 正式):允许
for range直接迭代自定义函数,实现惰性序列、流式管道,无需引入第三方迭代器库。 - ServeMux 路由增强(Go 1.22):标准库
net/http.ServeMux支持 HTTP 方法限定(GET /path)和通配符参数(/users/{id}),不再需要 Gorilla Mux 处理基础路由。 - loop variable 修复(Go 1.22):循环变量不再被所有迭代共享,历史上最常见的 goroutine 闭包 bug 从语言层面消除。
- iter 包(Go 1.23):标准库
iter包定义了迭代器类型规范iter.Seq[V]和iter.Seq2[K,V],配合slices、maps包形成统一的集合操作体系。 - toolchain 指令(Go 1.21+):
go.mod中的toolchain行精确锁定 Go 工具链版本,配合GOTOOLCHAIN=auto实现自动下载。
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 中的 i 和 v 是同一个变量在每轮迭代中被覆写。在闭包中捕获时,所有 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 内核态切换)。
- G(Goroutine):轻量级协程,初始栈 2KB,可按需增长至 1GB。包含执行状态、栈、PC 指针,由 Go 运行时管理。
- P(Processor):逻辑处理器,数量默认等于 CPU 核数(
GOMAXPROCS)。P 持有本地运行队列(Local Run Queue,LRQ),是 G 执行的必要资源。 - M(Machine):OS 线程,由操作系统调度。M 必须与 P 绑定才能执行 G。当 G 发生系统调用阻塞时,M 和 P 解绑,P 继续驱动其他 M 执行 G。
- 全局运行队列(GRQ):当 LRQ 满(超过 256 个 G)时,G 进入 GRQ。P 每执行 61 个 G 后会从 GRQ 取一个,避免 LRQ-only 导致的饥饿。
- Work Stealing:当某个 P 的 LRQ 为空时,它会从其他 P 的 LRQ 末尾"偷取"一半的 G,实现负载均衡。
┌─────────────────────────────────────────────────┐
│ 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.WithCancel 或 done 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),
)
}