Golang Go语言中 sync.Map tryLoadOrStore 函数看不懂其中的 ic := i
Golang Go语言中 sync.Map tryLoadOrStore 函数看不懂其中的 ic := i
// tryLoadOrStore atomically loads or stores a value if the entry is not
// expunged.
//
// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and
// returns with ok==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{})§, 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
}
}
}
为什么不用 ic := i
就会去堆上申请内存呢? 有巨佬或者彦祖知道吗~
整个函数感觉可以直接都放在 for 循环中,为什么要把前两个 if 判断单独拿出来?是因为放在 for 循环外有更好的性能吗
更多关于Golang Go语言中 sync.Map tryLoadOrStore 函数看不懂其中的 ic := i的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
今天刚好复习了一下[Gopher Con](
感觉和当中的主题十分类似,Golang 当中的 Stack Frame 是以 function 划分的,其中每个 function 对应着不同的内存区域,当 i 传递至 tryLoadOrStore 本质上是重新开辟了一层内存空间? ic 在当前当前作用于结束后(return)不再被使用,于是乎就直接分配在栈上没有再向上传递?不知道自己理解的对不对,还请大佬上来捞一下
总的来说,这里是个取舍吧,对于 tryLoadOrStore
来说, load
path 需要越快越好,而 store
path 可以稍微 costly 一些。
为什么不用 ic := i 就会去堆上申请内存呢
这个 我感觉是内存逃逸的问题吧, i 是一个 interface 类型的变量, 可以看做是传指针的, atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic))
直接传 i
的话, 因为 i 是一个外部变量, 函数作用域结束后还会存在, 所以 编译器在做内存逃逸分析的时候, 会分配到堆上
我是真的没有弄明白为什么要用 ic: = i 。· tryLoadOrStore(i interface{}) · 这里用的应该是 value semantic, i 是一个新的拷贝才对。 这是我的理解, 不对的地方请指出。
不好意思,上午的时候 没有仔细分析 说的有点问题。刚去翻了源码看了一下。又想了一下。
第一点,上午我说的 interface 类型的,可以看做指针是有问题的。其实 interface 也是一个类型。不能简单当做指针理解。
重点,为什么不用 ic := i 就会去堆上申请内存呢, 其实,这里还是内存逃逸的问题。
如果 i
传的是一个 非指针类型的变量, 那 在调用 tryLoadOrStore() 时,是复制的,
这里 atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic))
是取地址的,就会导致内存逃逸。
而加上 ic := i
之后, 后面取的是 ic
的地址,就不会导致 i
逃逸了
上一段代码就好理解了<br>func main() {<br>}<br><br>func fun1(i1 interface{}) {<br> atomic.CompareAndSwapPointer(nil, nil, unsafe.Pointer(&i1))<br>}<br><br>func fun2(i2 interface{}) {<br> ic := i2<br> atomic.CompareAndSwapPointer(nil, nil, unsafe.Pointer(&ic))<br>}<br>
用 go build -gcflags="-m -l" .\main.go
命令 做内存逃逸分析
结果是
.\main.go:11:11: moved to heap: i1
.\main.go:15:11: leaking param: i2
.\main.go:16:2: moved to heap: ic
i1 分配到堆上了 ic 分配到堆上了 i2 是在栈上的, 相当于通过一次复制, 阻断了内存逃逸
我也是菜鸡 说的可能也不对 目前来看 这样似乎能解释的通
简单说就是:
如果没有 ic:=i,无论是在 fast path 还是 slow path,i 必然都会逃逸到 heap 上。
但是在后面加上 ic := i,只会在 slow path 上,才会将 ic 分配在 heap 上。因为这个时候实际上是将 ic move to heap
谢谢回复。 有空了我多了解一下这方面的知识。
非科班。感觉是 i
是传参进来的,一开始它的地址一定在栈上,如果要保存走 unsafe 的 &i
的话需要 “手动”移动到堆上。ic
一开始编译器就知道放在堆上,不需要手动操作。
然后注释是说,如果在函数的前 7 行就返回了,还省下了在堆上申请 ic (相比于一上来就是一个 for )
在Golang的sync.Map
中,tryLoadOrStore
函数是一个比较底层且复杂的操作,它涉及到并发安全地加载或存储键值对。这里的ic := i
语句,虽然看似简单,但在理解整个函数的工作机制时,需要一些背景知识。
sync.Map
是为了在Go中提供一个比传统的map更高效且线程安全的并发数据结构。它内部使用了分段锁(sharding)等技术来减少锁竞争,提高并发性能。
在tryLoadOrStore
函数中,i
通常是一个索引,用于定位到sync.Map
内部的一个分片(segment)或者是一个特定的存储位置。ic
(可能是index or something similar的缩写)则是这个索引的一个副本,用于在后续的操作中避免对原始索引变量的修改,保证线程安全和数据一致性。
这个函数的主要任务是尝试在sync.Map
中加载一个键对应的值,如果不存在,则尝试存储这个键值对。在这个过程中,ic
(即i
的副本)会被用来定位到正确的存储位置,并可能涉及到对该位置的读写操作。
由于sync.Map
的内部实现较为复杂,且涉及到多个层面的并发控制和数据一致性保证,因此理解tryLoadOrStore
函数需要较为深入的知识。如果你对并发编程、锁机制以及Go语言的内存模型有一定的了解,那么理解这个函数会相对容易一些。