Golang中Sync.pool.Get()是随机工作的吗?(50行代码解析)
Golang中Sync.pool.Get()是随机工作的吗?(50行代码解析) 以下示例代码简单地展示了池(pool)的工作原理。
package main
import (
"fmt"
"math/rand"
"runtime"
"sync"
)
type Data struct {
tag string
buffer []int
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
pool := sync.Pool{
New: func() interface{} {
data := new(Data)
data.tag = "new"
data.buffer = make([]int, 10)
return data
},
}
wg := new(sync.WaitGroup)
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
data := pool.Get().(*Data)
for index := range data.buffer {
data.buffer[index] = rand.Intn(100)
}
fmt.Println(data)
data.tag = "used"
pool.Put(data)
}()
}
wg.Wait()
fmt.Println("done")
for i := 0; i < 3; i++ {
data := pool.Get().(*Data)
fmt.Println(data)
}
fmt.Scanln()
}
运行此代码后,会得到一些随机的 data 结构,例如:
❯ go run pool.go
&{new [81 87 47 59 81 18 25 40 56 0]}
&{new [94 11 62 89 28 74 11 45 37 6]}
&{new [95 66 28 58 47 47 87 88 90 15]}
done
&{used [95 66 28 58 47 47 87 88 90 15]}
&{new [0 0 0 0 0 0 0 0 0 0]}
&{used [94 11 62 89 28 74 11 45 37 6]}
再运行一次: ❯ go run pool.go &{new [81 87 47 59 81 18 25 40 56 0]} &{new [94 11 62 89 28 74 11 45 37 6]} &{new [95 66 28 58 47 47 87 88 90 15]} done &{used [81 87 47 59 81 18 25 40 56 0]} &{used [94 11 62 89 28 74 11 45 37 6]} &{used [95 66 28 58 47 47 87 88 90 15]}
此时,Get() 函数是随机返回的。这意味着无法保证返回池中的哪个数据。 因此,Sync.Pool 保证了并发访问的安全性,但无法提供选择特定数据的功能。 这就是我对 Sync.Pool 的理解。 但是,我不太了解它的实际使用场景。您能提供一些指导或经验分享吗?
抱歉我的英语表达可能不够好。
更多关于Golang中Sync.pool.Get()是随机工作的吗?(50行代码解析)的实战教程也可以访问 https://www.itying.com/category-94-b0.html
池并非设计用作您控制的缓冲区,它作为一种性能优化手段存在,旨在减少频繁堆分配对象对垃圾回收器的压力。Godoc 对 Pool 提供了很好的解释:
Pool 是一组可以单独保存和检索的临时对象。
存储在池中的任何项目都可能在任何时候被自动移除,且不会发出通知。 如果此时池持有唯一引用,该项目可能会被释放。
Pool 可安全地供多个 goroutine 同时使用。
Pool 的目的是缓存已分配但未使用的项目以供后续重用,从而减轻垃圾回收器的压力。也就是说,它使得构建高效、线程安全的空闲列表变得容易。然而,它并不适用于所有空闲列表。
Pool 的一个恰当用途是管理一组临时项目,这些项目在包的并发独立客户端之间静默共享,并可能被重用。Pool 提供了一种在许多客户端之间分摊分配开销的方法。
一个良好使用 Pool 的例子在 fmt 包中,它维护着一个动态大小的临时输出缓冲区存储。该存储会在负载下(当许多 goroutine 正在积极打印时)扩展,并在静止时收缩。
另一方面,作为短生命周期对象一部分维护的空闲列表不适合使用 Pool,因为在这种场景下开销无法很好地分摊。让此类对象实现自己的空闲列表会更高效。
Pool 在首次使用后不得被复制。
我加粗的部分值得您注意,这解释了为什么有时 Pool 不会返回您放回池中的对象,而是返回一个全新的对象,从而导致您所描述的随机行为。
更多关于Golang中Sync.pool.Get()是随机工作的吗?(50行代码解析)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
sync.Pool.Get() 不是随机工作的,但它的行为具有不确定性。这是由 Go 调度器的并发特性和 sync.Pool 的内部实现机制共同决定的。
核心机制解析
sync.Pool 为每个 P(处理器)维护一个本地池,并有一个共享池。当调用 Get() 时:
- 优先从当前 P 的本地池获取
- 如果本地池为空,尝试从其他 P 的池中窃取
- 如果都为空,调用
New函数创建新对象
这种设计是为了减少锁竞争,提高并发性能。
代码示例:展示不确定性
package main
import (
"fmt"
"sync"
"time"
)
func main() {
pool := &sync.Pool{
New: func() interface{} {
return "new-item"
},
}
// 预先放入一些数据
for i := 0; i < 5; i++ {
pool.Put(fmt.Sprintf("item-%d", i))
}
// 并发获取,结果顺序不确定
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
time.Sleep(time.Duration(id%3) * time.Millisecond)
item := pool.Get()
fmt.Printf("goroutine %d got: %v\n", id, item)
pool.Put(item) // 放回以便其他 goroutine 使用
}(i)
}
wg.Wait()
}
实际使用场景
1. 临时对象池(减少 GC 压力)
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) {
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset() // 重要:重置状态
buf.Write(data)
// 处理 buf
bufferPool.Put(buf)
}
2. 数据库连接临时缓存
var stmtPool = sync.Pool{
New: func() interface{} {
stmt, _ := db.Prepare("SELECT * FROM users WHERE id = ?")
return stmt
},
}
func GetUser(id int) {
stmt := stmtPool.Get().(*sql.Stmt)
defer stmtPool.Put(stmt)
row := stmt.QueryRow(id)
// 处理结果
}
3. JSON 编码器/解码器复用
var encoderPool = sync.Pool{
New: func() interface{} {
return json.NewEncoder(io.Discard)
},
}
func EncodeToJSON(v interface{}) ([]byte, error) {
encoder := encoderPool.Get().(*json.Encoder)
defer encoderPool.Put(encoder)
buf := new(bytes.Buffer)
encoder.SetEscapeHTML(false)
encoder.Encode(v)
return buf.Bytes(), nil
}
重要注意事项
- 对象状态重置:从池中取出的对象必须重置
data := pool.Get().(*Data)
data.Reset() // 清除旧数据
- 不要假设对象生命周期:池中的对象可能在任何时候被 GC
// 错误:假设对象会一直存在
pool.Put(createExpensiveObject())
// 正确:每次需要时都准备创建新对象
obj := pool.Get()
if obj == nil {
obj = createExpensiveObject()
}
- 适合存储临时、可复用的对象,不适合管理连接池等需要精确控制的对象。
sync.Pool 的核心价值在于减少内存分配和 GC 压力,特别是在高并发场景下。它的不确定性是设计上的权衡,以换取更好的性能。

