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 设计不一致:
Intn、Int31、Int63这种命名让人困惑 - 没有
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 中缺少 Uint32 和 Uint64 方法是一个长期被诟病的问题。在很多场景下(比如生成随机 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
}
自定义随机源
如果你需要可重现的随机数序列(比如测试),可以创建自己的 ChaCha8 或 PCG 源:
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/v2 和 math/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/v2 比 math/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 代码更舒服:
range over integers:终于不用写for i := 0; i < n; i++了math/rand/v2:更现代、更好用、支持泛型- ServeMux 路由增强:标准库的 HTTP 路由终于够用了(虽然还比不上第三方框架)
- 循环变量作用域改进:闭包陷阱成为历史
- 性能优化:PGO 和标准库持续改进
这些特性体现了 Go 团队"小步快跑、持续改进"的理念。Go 不追求一次性引入大量新特性,而是稳扎稳打地解决开发者的实际痛点。
下一篇,我们将深入 Go 1.23 的迭代器特性——那才是真正改变 Go 编程范式的大招。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。