Go 1.22 新特性:range over integers 和 math/rand/v2

深入探索 Go 1.22 的重要特性:range over integers、math/rand/v2 包、ServeMux 路由增强、性能优化与迁移指南

Go 1.22 新特性:range over integers 和 math/rand/v2

2024 年 2 月,Go 1.22 正式发布。这是 Go 语言进入第 15 个年头后的第一份"新年礼物"。

如果你以为这又是一个"修修 bug、调调性能"的小版本,那你可能会大吃一惊。Go 1.22 带来了几个让社区讨论热烈的改动——for range 终于支持整数了,math/rand 终于有了 v2 版本,net/http.ServeMux 终于能按 HTTP Method 路由了。

这些特性看起来都不"惊天动地",但每一个都精准地戳中了开发者日常写代码时的痛点。今天我们就来逐一拆解这些新特性。

在深入之前,我想先聊聊 Go 的版本发布节奏。Go 团队每年发布两个大版本(比如 1.21 和 1.22),每个版本都经过严格的测试和审查。这种"半年一更"的节奏保证了 Go 语言的稳定性——你不会遇到那种"升级一个版本整个项目就炸了"的情况。同时,每个版本都会带来一些实用的改进,让开发者感受到语言在不断进步。Go 1.22 就是这种理念的典型体现:没有大张旗鼓地引入全新范式,而是悄悄地把日常开发中的小烦恼一个个解决掉。

range over integers:告别 for i := 0; i < n; i++

这可能是 Go 1.22 最让人兴奋的小改动。

在 Go 1.22 之前,如果你想循环 N 次,只能写那种从 C 语言继承来的经典 for 循环:

package main

import "fmt"

func main() {
    // 😐 经典但啰嗦
    for i := 0; i < 5; i++ {
        fmt.Println("第", i, "次")
    }
}

这段代码有什么问题?它太"C"了。i := 0; i < n; i++ 这种写法几乎是所有命令式语言的标配,Go 虽然简化了语法,但本质上没有摆脱这种模式。而且每次写这个,你都得在心里默念一遍"从 0 开始、小于 n、每次加 1"——这不是在写业务逻辑,这是在写样板代码。

你可能会说,这有什么大不了的?不就是多写几个字符吗?但编程语言的进化史,本质上就是一部"减少样板代码"的历史。从汇编到 C,从 C 到 Java,从 Java 到 Go,每一次进步都让我们少写一点无聊的代码,多关注真正重要的业务逻辑。Python 有 for i in range(n),Rust 有 for i in 0..n,Kotlin 有 for (i in 0 until n),Go 在这方面一直落后。直到 Go 1.22,这个遗憾终于被补上了。

Go 1.22 引入了 range over integers

package main

import "fmt"

func main() {
    // ✨ Go 1.22: range over integer
    for i := range 5 {
        fmt.Println("第", i, "次")
    }
}

就这么简单。range 5 意味着 i 从 0 到 4,和 range 一个切片时的行为完全一致——都是从 0 开始,到 N-1 结束。

更多使用场景

1. 生成序列

package main

import "fmt"

func main() {
    // 生成 1 到 10 的数字
    var nums []int
    for i := range 10 {
        nums = append(nums, i+1)
    }
    fmt.Println(nums) // [1 2 3 4 5 6 7 8 9 10]
}

2. 初始化切片

package main

import "fmt"

func main() {
    // 创建一个长度为 5 的切片,每个元素是其索引的平方
    squares := make([]int, 5)
    for i := range len(squares) {
        squares[i] = i * i
    }
    fmt.Println(squares) // [0 1 4 9 16]
}

3. 循环固定次数(不关心索引)

package main

import "fmt"

func main() {
    // 重复执行 3 次
    for range 3 {
        fmt.Println("Hello, Go 1.22!")
    }
}

注意最后一行代码——当不需要索引时,可以省略 i :=,这和 for range channel 的语法是一致的。

4. 配合泛型和切片

package main

import "fmt"

// FillWithIndex 用索引值填充切片
func FillWithIndex[T any](s []T, f func(int) T) {
    for i := range len(s) {
        s[i] = f(i)
    }
}

func main() {
    names := make([]string, 5)
    FillWithIndex(names, func(i int) string {
        return fmt.Sprintf("item-%d", i)
    })
    fmt.Println(names)
    // [item-0 item-1 item-2 item-3 item-4]
}

变量作用域的改进

Go 1.22 还有一个容易被忽视的改动:for 循环变量的作用域从"整个循环共享一个变量"变成了"每次迭代一个变量"。

这意味着经典的闭包陷阱消失了:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Go 1.21 及之前:所有 goroutine 共享同一个 v
    // Go 1.22 及之后:每次迭代都有自己的 v
    for v := range []int{1, 2, 3, 4, 5} {
        go func() {
            fmt.Println(v)
        }()
    }
    time.Sleep(100 * time.Millisecond)
}

在 Go 1.21 及之前,这段代码的输出可能是 5 5 5 5 5(因为所有 goroutine 共享同一个 v,循环结束时 v = 5)。在 Go 1.22 中,每次迭代都有自己的 v,输出会是 1 2 3 4 5(顺序可能不同)。

这个改动让 vet 工具不再报 loopclosure 警告,也让 Go 代码更安全、更符合直觉。

注意事项

range over integers 有几个细节值得注意:

package main

import "fmt"

func main() {
    // ✅ 可以 range 一个表达式
    n := 10
    for i := range n * 2 {
        if i%5 == 0 {
            fmt.Println(i)
        }
    }

    // ✅ 可以 range 一个函数调用
    for i := range computeLimit() {
        fmt.Println(i)
    }

    // ❌ 不能 range 负数(运行时会 panic)
    // for i := range -1 { }  // 编译错误:cannot range -1 (constant of type int)
}

func computeLimit() int {
    return 5
}

math/rand/v2:更现代、更好用的随机数

math/rand 是 Go 标准库中一个让人又爱又恨的包。它从 Go 1.0 就有了,但设计上有几个长期被诟病的问题:

  • 全局随机数生成器默认不自动 seed:在 Go 1.20 之前,你必须手动 rand.Seed(time.Now().UnixNano()),否则每次运行程序随机数都一样
  • API 设计不一致IntnInt31Int63 这种命名让人困惑
  • 没有 Uint32/Uint64 方法
  • 不支持泛型

这些问题的根源在于 math/rand 的设计可以追溯到 Go 语言的早期。那时候 Go 还没有泛型,API 设计风格也还在摸索中。随着语言的成熟,这些历史包袱越来越显得格格不入。但直接修改 math/rand 会破坏向后兼容性——这是 Go 团队最不愿意做的事情。

Go 1.22 的解决方案非常巧妙:推出 math/rand/v2。利用 Go modules 的版本管理功能,v2 作为一个全新的包发布,既不影响 v1 的用户,又可以大胆地重新设计 API。这是 Go 生态中"渐进式改进"的一个典范——旧的代码继续工作,新的代码可以享受更好的设计。

package main

import (
    "fmt"
    "math/rand/v2"
)

func main() {
    // ✅ 不需要 Seed!默认自动初始化
    fmt.Println(rand.Int())     // 非负伪随机 int
    fmt.Println(rand.IntN(100)) // [0, 100) 的随机整数
    
    // ✅ 更直观的命名
    fmt.Println(rand.Int32())   // 随机 int32
    fmt.Println(rand.Int64())   // 随机 int64
    fmt.Println(rand.Uint32())  // 随机 uint32(终于有了!)
    fmt.Println(rand.Uint64())  // 随机 uint64
    
    // ✅ 浮点数
    fmt.Println(rand.Float32()) // [0.0, 1.0)
    fmt.Println(rand.Float64()) // [0.0, 1.0)
}

命名改进对比

math/rand (v1)math/rand/v2说明
rand.Intn(n)rand.IntN(n)大写 N 更清晰
rand.Int31()rand.Int32()不再用"31"这种奇怪名字
rand.Int63()rand.Int64()同上
rand.Int31n(n)rand.Int32N(n)组合命名
rand.Uint32()新增
rand.Uint64()新增
rand.Seed()已移除不再需要

这个命名改进看起来很小,但背后反映的是 Go 团队对 API 设计一致性的追求。在 v1 中,Int31 这个名字让人困惑——它到底是返回 31 位的 int32,还是返回一个 31 位的 int?实际上它返回的是一个非负的 int32,但名字完全没有表达这一点。v2 直接用 Int32 表示返回类型,用 IntN 表示"返回 [0, N) 范围的整数",语义清晰得多。

另外,v1 中缺少 Uint32Uint64 方法是一个长期被诟病的问题。在很多场景下(比如生成随机 ID、哈希种子),我们需要的就是无符号整数,v1 却逼着你先拿到一个有符号整数再转换,非常别扭。v2 终于补上了这个缺口。

泛型支持

math/rand/v2 引入了泛型函数,让随机数操作更灵活:

package main

import (
    "fmt"
    "math/rand/v2"
)

func main() {
    // Shuffle 打乱切片
    cards := []string{"A", "B", "C", "D", "E"}
    rand.Shuffle(len(cards), func(i, j int) {
        cards[i], cards[j] = cards[j], cards[i]
    })
    fmt.Println("打乱后:", cards)
    
    // Perm 生成随机排列
    perm := rand.Perm(5)
    fmt.Println("随机排列:", perm)
    
    // N 泛型函数:生成指定类型的随机数
    fmt.Println(rand.N(100))     // int
    fmt.Println(rand.N(int32(50)))  // int32
    fmt.Println(rand.N(uint64(1000))) // uint64
}

自定义随机源

如果你需要可重现的随机数序列(比如测试),可以创建自己的 ChaCha8PCG 源:

package main

import (
    "fmt"
    "math/rand/v2"
)

func main() {
    // 创建可重现的随机源
    var seed [32]byte
    seed[0] = 42 // 设置一个种子
    src := rand.NewChaCha8(seed)
    r := rand.New(src)
    
    // 使用自定义源生成随机数
    for i := range 5 {
        fmt.Printf("第 %d 次: %d\n", i+1, r.IntN(100))
    }
    
    // 重新创建相同的源,得到相同的序列
    src2 := rand.NewChaCha8(seed)
    r2 := rand.New(src2)
    
    fmt.Println("\n重现序列:")
    for i := range 5 {
        fmt.Printf("第 %d 次: %d\n", i+1, r2.IntN(100))
    }
    // 输出和上面完全一致!
}

math/rand/v2 提供了两种随机源:

  • ChaCha8:加密学安全的伪随机数生成器(CSPRNG),质量更高
  • PCG:快速、轻量的伪随机数生成器,适合性能敏感的场景

实际应用:生成随机密码

package main

import (
    "fmt"
    "math/rand/v2"
    "strings"
)

const (
    lowercase = "abcdefghijklmnopqrstuvwxyz"
    uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    digits    = "0123456789"
    symbols   = "!@#$%^&*"
)

func generatePassword(length int) string {
    charset := lowercase + uppercase + digits + symbols
    var b strings.Builder
    b.Grow(length)
    
    for range length {
        b.WriteByte(charset[rand.IntN(len(charset))])
    }
    
    return b.String()
}

func main() {
    for range 5 {
        fmt.Println(generatePassword(16))
    }
}

随机数的性能对比

如果你对性能非常敏感,可能会好奇 math/rand/v2math/rand 的性能差异。让我们做个简单的基准测试:

package main

import (
    "fmt"
    "math/rand"
    randv2 "math/rand/v2"
    "time"
)

func main() {
    const iterations = 10000000

    // 测试 v1
    start := time.Now()
    for range iterations {
        _ = rand.Intn(1000)
    }
    v1Duration := time.Since(start)

    // 测试 v2
    start = time.Now()
    for range iterations {
        _ = randv2.IntN(1000)
    }
    v2Duration := time.Since(start)

    fmt.Printf("math/rand    (v1): %v\n", v1Duration)
    fmt.Printf("math/rand/v2 (v2): %v\n", v2Duration)
    fmt.Printf("v2 相对 v1 的性能: %.2f%%\n",
        float64(v1Duration)/float64(v2Duration)*100)
}

在我的机器上(Apple M2),math/rand/v2math/rand 快大约 10-20%。这主要得益于 v2 使用了更现代的随机数算法(PCG),以及去掉了全局锁(v1 的全局方法每次调用都要加锁)。

当然,对于绝大多数应用来说,这个性能差异可以忽略不计。但如果你在写高频交易系统、游戏服务器或者科学计算程序,每一点性能提升都值得考虑。

net/http.ServeMux 路由增强

Go 1.22 对 net/http.ServeMux 做了一个看似微小但影响深远的改进:支持 HTTP Method + Path Pattern 的路由模式

这个改动为什么重要?因为 HTTP 路由是 Web 开发中最基础的需求之一。在 Go 1.22 之前,如果你想要一个像样的路由系统,几乎必须引入第三方框架——Gin、Echo、Chi、Fiber……这些框架的核心价值之一就是路由。标准库的 ServeMux 因为功能太弱,长期以来被开发者戏称为"玩具级路由器"。

Go 1.22 的改进虽然不能让 ServeMux 和第三方框架完全媲美,但对于简单的 API 服务来说,它已经足够好用了。这意味着很多小型项目可以不再依赖第三方路由框架,直接使用标准库就能完成工作。

在 Go 1.21 中,ServeMux 的路由模式是这样的:

// Go 1.21 及之前
mux.HandleFunc("/users/", handleUsers)  // 所有方法都匹配
mux.HandleFunc("/users", handleUserList)

问题在于,无论 GET、POST、PUT、DELETE,只要路径匹配就会路由到同一个 handler。你必须在 handler 内部手动检查 HTTP 方法:

func handleUsers(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        // 处理 GET
    case "POST":
        // 处理 POST
    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

Go 1.22 的路由模式

Go 1.22 的 ServeMux 支持 METHOD /path 的格式:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    
    // ✅ Go 1.22: Method + Path 路由
    mux.HandleFunc("GET /users/{id}", getUser)
    mux.HandleFunc("POST /users", createUser)
    mux.HandleFunc("PUT /users/{id}", updateUser)
    mux.HandleFunc("DELETE /users/{id}", deleteUser)
    
    // 也支持不带 Method 的路由(匹配所有方法)
    mux.HandleFunc("/health", healthCheck)
    
    // 还支持通配符
    mux.HandleFunc("GET /static/", serveStatic)
    
    fmt.Println("Server starting on :8080...")
    http.ListenAndServe(":8080", mux)
}

func getUser(w http.ResponseWriter, r *http.Request) {
    // ✅ 可以通过 PathValue 提取路径参数
    id := r.PathValue("id")
    fmt.Fprintf(w, "Get user: %s", id)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Create user")
}

func updateUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, "Update user: %s", id)
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, "Delete user: %s", id)
}

func healthCheck(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "OK")
}

func serveStatic(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Serving: %s", r.URL.Path)
}

路径参数提取

r.PathValue() 是配合路由模式使用的方法,可以提取 {name} 占位符的值:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    
    // 多个路径参数
    mux.HandleFunc("GET /api/v{version}/users/{id}/posts/{postId}", getPost)
    
    http.ListenAndServe(":8080", mux)
}

func getPost(w http.ResponseWriter, r *http.Request) {
    version := r.PathValue("version")
    userID := r.PathValue("id")
    postID := r.PathValue("postId")
    
    fmt.Fprintf(w, "Version: %s, User: %s, Post: %s\n",
        version, userID, postID)
}

// 请求: GET /api/v2/users/alice/posts/42
// 输出: Version: 2, User: alice, Post: 42

完整的 RESTful API 示例

让我们用 Go 1.22 的 ServeMux 写一个完整的 RESTful API:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
)

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

type UserStore struct {
    mu    sync.RWMutex
    users map[string]User
}

func NewUserStore() *UserStore {
    return &UserStore{users: make(map[string]User)}
}

func (s *UserStore) Get(id string) (User, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    u, ok := s.users[id]
    return u, ok
}

func (s *UserStore) Create(u User) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.users[u.ID] = u
}

func (s *UserStore) Update(u User) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, ok := s.users[u.ID]; !ok {
        return false
    }
    s.users[u.ID] = u
    return true
}

func (s *UserStore) Delete(id string) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, ok := s.users[id]; !ok {
        return false
    }
    delete(s.users, id)
    return true
}

func (s *UserStore) List() []User {
    s.mu.RLock()
    defer s.mu.RUnlock()
    users := make([]User, 0, len(s.users))
    for _, u := range s.users {
        users = append(users, u)
    }
    return users
}

func main() {
    store := NewUserStore()
    mux := http.NewServeMux()
    
    // GET /users - 列表
    mux.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
        users := store.List()
        json.NewEncoder(w).Encode(users)
    })
    
    // POST /users - 创建
    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
        }
        store.Create(u)
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(u)
    })
    
    // GET /users/{id} - 查询
    mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := r.PathValue("id")
        u, ok := store.Get(id)
        if !ok {
            http.Error(w, "user not found", http.StatusNotFound)
            return
        }
        json.NewEncoder(w).Encode(u)
    })
    
    // PUT /users/{id} - 更新
    mux.HandleFunc("PUT /users/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := r.PathValue("id")
        var u User
        if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        u.ID = id
        if !store.Update(u) {
            http.Error(w, "user not found", http.StatusNotFound)
            return
        }
        json.NewEncoder(w).Encode(u)
    })
    
    // DELETE /users/{id} - 删除
    mux.HandleFunc("DELETE /users/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := r.PathValue("id")
        if !store.Delete(id) {
            http.Error(w, "user not found", http.StatusNotFound)
            return
        }
        w.WriteHeader(http.StatusNoContent)
    })
    
    log.Println("API Server on :8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
}

注意: Go 1.22 的 ServeMux 虽然增强了路由能力,但还不支持中间件(middleware)和正则表达式匹配。对于复杂场景,仍然推荐使用 Chi、Gin 或 Echo 等第三方路由器。

ServeMux 路由的局限性

虽然 Go 1.22 的 ServeMux 路由能力大幅提升,但我们也要清醒地认识它的局限性:

1. 不支持正则表达式

// ❌ 不支持
mux.HandleFunc("GET /users/{id:[0-9]+}", getUser) // 编译错误

// ✅ 只能在 handler 内部验证
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    // 手动验证 id 是否为数字
    if _, err := strconv.Atoi(id); err != nil {
        http.Error(w, "invalid user id", http.StatusBadRequest)
        return
    }
    // 处理请求...
})

2. 不支持可选参数

// ❌ 不支持可选的 {id}
mux.HandleFunc("GET /users/{id?}", handler) // 编译错误

// ✅ 需要注册两个路由
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("GET /users/{id}", getUser)

3. 不支持通配符匹配

// ❌ 不支持 * 通配符
mux.HandleFunc("GET /files/*path", serveFile) // 编译错误

// ✅ 只能用后缀 / 匹配子路径
mux.HandleFunc("GET /files/", func(w http.ResponseWriter, r *http.Request) {
    path := strings.TrimPrefix(r.URL.Path, "/files/")
    // 处理文件路径...
})

对于简单的 RESTful API,Go 1.22 的 ServeMux 已经够用了。但如果你需要复杂的路由规则(正则匹配、参数验证、路由分组、中间件链),还是老老实实用 Chi、Gin 或 Echo 吧。

性能改进

Go 1.22 在性能方面也有不少优化:

PGO(Profile-Guided Optimization)改进

Go 1.21 引入了 PGO,Go 1.22 在此基础上做了进一步优化。使用 PGO 后,常见工作负载可以获得 2-7% 的性能提升。

// 收集 CPU profile
// go build -pgo=auto  # 默认自动使用 pgo.pprof
// go build -pgo=profile.pprof  # 手动指定

标准库性能优化

  • encoding/json:编码和解码性能提升约 10-20%
  • crypto/tls:TLS 1.3 握手性能优化
  • net/http:HTTP/2 性能改进
package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Record struct {
    ID      int       `json:"id"`
    Name    string    `json:"name"`
    Score   float64   `json:"score"`
    Created time.Time `json:"created"`
}

func main() {
    // 生成测试数据
    records := make([]Record, 100000)
    for i := range len(records) {
        records[i] = Record{
            ID:      i,
            Name:    fmt.Sprintf("user-%d", i),
            Score:   float64(i) * 1.5,
            Created: time.Now(),
        }
    }
    
    start := time.Now()
    data, _ := json.Marshal(records)
    fmt.Printf("编码 %d 条记录: %v\n", len(records), time.Since(start))
    fmt.Printf("JSON 大小: %.2f MB\n", float64(len(data))/(1024*1024))
    
    start = time.Now()
    var decoded []Record
    json.Unmarshal(data, &decoded)
    fmt.Printf("解码 %d 条记录: %v\n", len(decoded), time.Since(start))
}

迁移指南

从 Go 1.21 升级到 Go 1.22

1. 更新 go.mod

module example.com/myproject

go 1.22  // 更新到 1.22

2. 使用 range over integers 简化循环

// Before (Go 1.21)
for i := 0; i < n; i++ {
    process(i)
}

// After (Go 1.22)
for i := range n {
    process(i)
}

3. 迁移到 math/rand/v2

// Before
import "math/rand"
n := rand.Intn(100)

// After
import "math/rand/v2"
n := rand.IntN(100)  // 注意 N 是大写

4. 简化 HTTP 路由

// Before
mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        // ...
    case "POST":
        // ...
    }
})

// After
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("POST /users", createUser)

兼容性注意事项

  • 循环变量作用域改变:如果你的代码依赖于旧的"循环变量共享"行为(虽然这通常是个 bug),升级后行为会改变。检查你的代码中是否有闭包捕获循环变量的情况。
  • math/rand 默认行为:在 Go 1.20+ 中,全局随机数生成器会自动 seed。如果你之前的代码依赖于 rand.Seed() 的确定性行为,需要改用自定义源。
  • ServeMux 路由匹配优先级:新的路由模式更精确,如果有冲突的路由,需要检查匹配规则。

实战案例:构建一个简单的任务队列

让我们用 Go 1.22 的新特性构建一个完整的任务队列系统:

package main

import (
    "fmt"
    "math/rand/v2"
    "sync"
    "time"
)

type Task struct {
    ID      int
    Payload string
    Status  string
}

type TaskQueue struct {
    mu       sync.Mutex
    tasks    []*Task
    nextID   int
}

func NewTaskQueue() *TaskQueue {
    return &TaskQueue{}
}

func (q *TaskQueue) Enqueue(payload string) *Task {
    q.mu.Lock()
    defer q.mu.Unlock()
    
    task := &Task{
        ID:      q.nextID,
        Payload: payload,
        Status:  "pending",
    }
    q.tasks = append(q.tasks, task)
    q.nextID++
    return task
}

func (q *TaskQueue) Process(workerID int) {
    for i := range 5 { // 使用 range over integers
        q.mu.Lock()
        if len(q.tasks) == 0 {
            q.mu.Unlock()
            return
        }
        
        task := q.tasks[0]
        q.tasks = q.tasks[1:]
        q.mu.Unlock()
        
        task.Status = "processing"
        fmt.Printf("Worker %d: 处理任务 %d\n", workerID, task.ID)
        
        // 模拟处理时间
        time.Sleep(time.Millisecond * time.Duration(rand.IntN(100)))
        
        task.Status = "completed"
        fmt.Printf("Worker %d: 完成任务 %d\n", workerID, task.ID)
        
        _ = i // 避免未使用变量警告
    }
}

func main() {
    queue := NewTaskQueue()
    
    // 添加任务
    for i := range 10 {
        queue.Enqueue(fmt.Sprintf("Task payload %d", i))
    }
    
    fmt.Printf("添加了 10 个任务\n")
    
    // 启动 3 个 worker
    var wg sync.WaitGroup
    for i := range 3 {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            queue.Process(workerID)
        }(i)
    }
    
    wg.Wait()
    fmt.Println("所有任务完成")
}

这个示例展示了如何在实际项目中使用 Go 1.22 的新特性:

  • range over integers 简化循环
  • math/rand/v2 生成随机延迟
  • 简洁的语法让代码更易读

总结

Go 1.22 是一个"实用主义"的版本,没有惊天动地的大特性,但每一个改动都让日常写 Go 代码更舒服:

  1. range over integers:终于不用写 for i := 0; i < n; i++
  2. math/rand/v2:更现代、更好用、支持泛型
  3. ServeMux 路由增强:标准库的 HTTP 路由终于够用了(虽然还比不上第三方框架)
  4. 循环变量作用域改进:闭包陷阱成为历史
  5. 性能优化:PGO 和标准库持续改进

这些特性体现了 Go 团队"小步快跑、持续改进"的理念。Go 不追求一次性引入大量新特性,而是稳扎稳打地解决开发者的实际痛点。

下一篇,我们将深入 Go 1.23 的迭代器特性——那才是真正改变 Go 编程范式的大招。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页