乐于分享
好东西不私藏

一起读Go源码(第16期) sync:Pool和Map

一起读Go源码(第16期) sync:Pool和Map

概述

朋友们好,今天我们来看sync包的PoolMap

Pool

sync.Pool是Go提供的临时对象池,用于存储和复用临时对象,用于减少因频繁创建和销毁相同类型对象带来的内存分配和GC压力(还是并发安全的)。例如在高频创建同类对象,HTTP服务中每个请求都要bytes.Buffer来构建响应体,若使用Pool,当完成一个请求时将bytes.Buffer放回池中,后续请求可以直接从池中取出已存在的buffer,无需重新分配内存了。下面是一个Pool的简单用例,创建Pool后调用Get从池中获取对象,此时池空,调用New返回hello;使用Puthello world放入池中;第二次取出得到池中有hello world;第三第四次取出时池中也是空,因此调用New返回hello。

pool := &sync.Pool{    New:func() interface{} {        return "hello"    },}obj := pool.Get()log.Println(obj)pool.Put("hello world")obj2 := pool.Get()log.Println(obj2)obj3 := pool.Get()log.Println(obj3)obj4 := pool.Get()log.Println(obj4)/*2026/04/15 19:18:24 hello2026/04/15 19:18:24 hello world2026/04/15 19:18:24 hello2026/04/15 19:18:24 hello*/

Pool 结构体详解

翻译Pool的源码注释,Pool是一组临时对象,这些对象可以被单独保存和检索。池中存储的任何对象都可能随时被自动移除,且不会收到通知。如果此时 Pool 持有该对象的唯一引用,该对象可能会被释放;Pool 可以被多个 goroutine 同时安全使用。Pool 的目的是缓存已分配但未使用的对象,以便后续重用从而减轻垃圾回收器的压力。也就是说,它可以轻松构建高效、线程安全的自由列表。但它并不适用于所有自由列表。Pool 在首次使用后不得被复制。

type Pool struct {    noCopy noCopy   // 防止 Pool 被拷贝的标记    local     unsafe.Pointer // 每个 P 的本地池,实际类型是 [P]poolLocal 的指针    localSize uintptr        // local 数组的长度(即 P 的数量)    victim     unsafe.Pointer // 上一轮 GC 清空后的本地池(用于延迟释放)    victimSize uintptr        // victim 数组的长度    Newfunc() any // 当 Get 无法从池中取到对象时,用于生成新值的函数}

Pool  Get方法

注释的意思是,Get从Pool中选择一个任意对象将其从 Pool 中移除,并返回给调用者。看下代码流程:

  1. 1. 竞态检测(之前好像在哪见过)
  2. 2. p.pin固定当前goroutine到某个P,返回对应的poolLocal和pid。
  3. 3. l.private尝试获取private对象,若对象为空,尝试从当前P的共享队列取出一个
  4. 4. 若本地队列没有,则走慢路径p.getSlow(pid)
  5. 5. runtime_procUnpin(),允许goroutine被调度到其他P
// Get selects an arbitrary item from the [Pool], removes it from the// Pool, and returns it to the caller.// Get may choose to ignore the pool and treat it as empty.// Callers should not assume any relation between values passed to [Pool.Put] and// the values returned by Get.//// If Get would otherwise return nil and p.New is non-nil, Get returns// the result of calling p.New.func (p *Pool) Get() any {    if race.Enabled {        race.Disable()    }    l, pid := p.pin()    x := l.private    l.private = nil    if x == nil {        // Try to pop the head of the local shard. We prefer        // the head over the tail for temporal locality of        // reuse.        x, _ = l.shared.popHead()        if x == nil {            x = p.getSlow(pid)        }    }    runtime_procUnpin()    if race.Enabled {        race.Enable()        if x != nil {            race.Acquire(poolRaceAddr(x))        }    }    if x == nil && p.New != nil {        x = p.New()    }    return x}

Pool  Put方法

Put方法将x添加到池中。步骤如下:

  1. 1. 若值为nil直接返回
  2. 2. 竞态检测(上面也看到了)
  3. 3. 固定当前goroutine到某个P,获取对应的poolLocal
  4. 4. 尝试将对象放入private字段,若private被占用,则推入当前P的共享对头头部。
  5. 5. 解除固定,竞态检测结束。
// Put adds x to the pool.func (p *Pool) Put(x any) {    if x == nil {        return    }    if race.Enabled {        if runtime_randn(4) == 0 {            // Randomly drop x on floor.            return        }        race.ReleaseMerge(poolRaceAddr(x))        race.Disable()    }    l, _ := p.pin()    if l.private == nil {        l.private = x    } else {        l.shared.pushHead(x)    }    runtime_procUnpin()    if race.Enabled {        race.Enable()    }}

Map

Map是Go提供的并发安全的map类型,专为简化多goroutine并发读写场景设计,这种Map特别适合读多写少的场景;下面是一个sync.Map示例,使用Store方法存储键值对,读取方法Load,若读不到就存储LoadOrStore方法,删除方法Delete,遍历键值对Range方法。

    var m sync.Map    m.Store("k1", 1)    m.Store("k2", 2)    if k, ok := m.Load("k1"); ok {        log.Println(k)    }    c, loaded := m.LoadOrStore("k3", 3)    log.Println(c, loaded)    m.Delete("k3")    m.Range(func(k, v interface{}) bool {        log.Println(k, v)        return true    })/*2026/04/15 19:53:03 12026/04/15 19:53:03 3 false2026/04/15 19:53:03 k1 12026/04/15 19:53:03 k2 2*/

Map结构体解析

Map类似于Go的map[any]any,但它可以被多个goroutine并发安全地使用,无需额外的锁或协调。Load、Store 和 Delete 操作均摊常数时间。Map 类型针对两种常见用例进行了优化:1.某个键的条目只被写入一次,但被读取很多次(例如只增长的缓存);2.多个 goroutine 对不相交的键集合进行读取、写入和覆盖。这两种情况下,与使用单独Mutex或RWMutex保护的Go map相比,使用Map可以显著减少锁竞争

下面来看Map结构体:

  1. 1. mu,互斥锁,保护并发安全。
  2. 2. read,原子指针,指向readOnly结构体
  3. 3. dirty,普通map
  4. 4. misses,计数器,统计read中查找失败,加锁查询dirty的次数;
type Map struct {    mu Mutex    read atomic.Pointer[readOnly]    dirty map[any]*entry    misses int}

在实际开发中,sync.Map方法的使用场景相对有限,日常业务开发中很少会用到,因此就不具体解读其方法的源码细节了Map具体方法如下:

方法
作用
Store(key, value)
存储键值对。
Load(key)
根据 key 读取值,返回 (value, bool)
LoadOrStore(key, value)
原子性地“读取或存储”:若 key 存在则返回现有值;否则存储新值。
Delete(key)
删除键值对。
Range(f func(key, value) bool)
遍历 map 中所有键值对。若回调函数返回 false,则停止遍历

写在最后

总结来说,sync.Pool核心是对象复用,可降低内存分配和GC压力,适用于高频创建同类临时对象,需注意其对象可能被GC回收,不可存储长期对象;sync.Map是并发安全map,优化读多写少的场景、减少锁竞争(比自己写一个来实现性能更好)。本人是新手小白,如果这篇笔记中有任何错误或不准确之处,真诚地希望各位读者能够给予批评和指正,如有更好的实现方法请给我留言,谢谢!欢迎大家在评论区留言!觉得写得还不错的话欢迎大家关注一波!