sync.Map是一个并发安全的MAP实现.sync.Map中的原始 map 类型实际上是map[any]*entry.

当我们调用Map.LoadOrStore并且条目存在时,将调用entry.tryLoadOrStore,并且以下是该函数的代码

func (e *entry) tryLoadOrStore(i any) (actual any, loaded, ok bool) {
    p := e.p.Load()
    if p == expunged {
        return nil, false, false
    }
    if p != nil {
        return *p, true, true
    }

    // Copy the interface after the first load to make this method more amenable
    // to escape analysis: if we hit the "load" path or the entry is expunged, we
    // shouldn't bother heap-allocating.
    ic := i
    for {
        if e.p.CompareAndSwap(nil, &ic) {
            return i, false, true
        }
        p = e.p.Load()
        if p == expunged {
            return nil, false, false
        }
        if p != nil {
            return *p, true, true
        }
    }
}

这是另一个函数trySwap,当我们调用SwapStore时,这个函数也会被调用.

func (e *entry) trySwap(i *any) (*any, bool) {
    for {
        p := e.p.Load()
        if p == expunged {
            return nil, false
        }
        if e.p.CompareAndSwap(p, i) {
            return p, true
        }
    }
}

tryLoadOrStore可以像trySwap一样基于其逻辑来实现,但它不是.我的问题是:既然它们的逻辑相似,为什么它们不以相同的方式实现?

当我试图理解时,我认为这是因为参数类型的不同,如果I*any,它不需要做复制,因为它已经是一个指针,我们不需要关心转义分析.但似乎没有从外部呼叫者那里获取地址的特殊原因.

    if e, ok := read.m[key]; ok {
        if v, ok := e.trySwap(&value); ok {
            if v == nil {
                return nil, false
            }
            return *v, true
        }
    }

那么我就不知道为什么这两个功能(和其他功能)以不同的方式实现.

推荐答案

弗里斯特,a quote岁,来自布莱恩·米尔斯,sync.Map的原始作者:

从一开始,sync.Map就相当粗糙了!

sync.Map中的代码对逸出分析非常敏感,实现由Benchmark驱动.

让我们深入研究提交历史.它应该有助于我们理解为什么它们以不同的方式实现.

The initial implementation in CL 37342:

  1. first patchset中的草案实施情况类似:
func (e *entry) tryStore(i interface{}) bool {
    for {
        p := atomic.LoadPointer(&e.p)
        if p == expunged {
            return false
        }
        if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(&i)) {
            return true
        }
    }
}

func (e *entry) tryLoadOrStore(i interface{}) (actual interface{}, loaded, clean bool) {
    for {
        p := atomic.LoadPointer(&e.p)
        if p == expunged {
            return nil, false, false
        }
        if p != nil {
            return *(*interface{})(p), true, true
        }
        if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&i)) {
            return i, false, true
        }
    }
}
  1. patchset 3中的两个实现中都添加了复制接口的技巧:
// Copy the interface to make this method more amenable to escape analysis:
// if we hit the "load" path or the entry is expunged, we shouldn't bother
// heap-allocating.
ic := i
  1. (*entry).tryStore被修改为接受patchset 5中的指针:

我找不到对这一变化的 comments .这很可能是逃逸分析和基准测试的结果.

func (e *entry) tryStore(i *interface{}) bool {
    p := atomic.LoadPointer(&e.p)
    if p == expunged {
        return false
    }
    for {
        if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
            return true
        }
        p = atomic.LoadPointer(&e.p)
        if p == expunged {
            return false
        }
    }
}

func (e *entry) tryLoadOrStore(i interface{}) (actual interface{}, loaded, ok bool) {
    p := atomic.LoadPointer(&e.p)
    if p == expunged {
        return nil, false, false
    }
    if p != nil {
        return *(*interface{})(p), true, true
    }

    // Copy the interface after the first load to make this method more amenable
    // to escape analysis: if we hit the "load" path or the entry is expunged, we
    // shouldn't bother heap-allocating.
    ic := i
    for {
        if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
            return i, false, true
        }
        p = atomic.LoadPointer(&e.p)
        if p == expunged {
            return nil, false, false
        }
        if p != nil {
            return *(*interface{})(p), true, true
        }
    }
}

(*entry).tryStore was simplified in CL 137441:

该更改阻止了对堆的这种转义:

sync/map.go:178:26: &e.p escapes to heap
sync/map.go:178:26:     from &e.p (passed to call[argument escapes]) at
func (e *entry) tryStore(i *interface{}) bool {
    for {
        p := atomic.LoadPointer(&e.p)
        if p == expunged {
            return false
        }
        if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
            return true
        }
    }
}

(*entry).tryStore was renamed to (*entry).trySwap in CL 399094:

func (e *entry) trySwap(i *any) (*any, bool) {
   for {
       p := e.p.Load()
       if p == expunged {
           return nil, false
       }
       if e.p.CompareAndSwap(p, i) {
           return p, true
       }
   }
}

就这样.

Note:其他一些小的CLS没有列出,例如,将实现切换到使用atomic.PointerCL 426074.

Go相关问答推荐

Golang ==错误:OCI运行时创建失败:无法启动容器进程:exec:./" bin:stat./" bin:没有这样的文件或目录:未知

如何修复在Go API中使用Gin Framework的请求资源上没有使用Gin Framework的请求源的消息?''

难以为多个平台添加Go Bazel构建选项

按位移计算结果中的差异

如何从 Go Lambda 函数返回 HTML?

为什么我只收到部分错误而不是我启动的 goroutines 的所有错误?

从 eBPF LRU 哈希映射中错误驱逐的元素

Go 中如何调用测试函数?

创建新对象后如何返回嵌套实体?

如何在模板中传递和访问 struct 片段和 struct

读取非UTF8编码的文件内容并正确打印出来

枚举的 Golang 验证器自定义验证规则

确保 Go 1.20 编译时的严格可比性?

为什么当我忽略 template.New() 程序可以成功运行?

将shell输出绑定到Go中的 struct 的最佳方法?

使用 AppID 在 Windows 中启动应用程序并获取 pid

github.com/rs/zerolog 字段的延迟判断

Golang API 的 HTTPS

try 创建新的 etcdv3 客户端时出现pc error: code = Unavailable desc = error reading from server: EOF

获取单调时间,同 CLOCK_MONOTONIC