我的应用程序使用 sync.Map
来存储通过多个 goroutine 并发访问的打开套接字连接。
我想知道是将这些连接存储为结构体 net.Conn
还是引用 *net.Conn
。
这两种选择的优点/缺点是什么?首选解决方案是什么?
最佳答案
虽然@blackgreen 是正确的,但我会稍微扩展一下推理。
sync.Map
类型被显式定义为在 interface{}
上运行.
现在请记住,在 Go 中,接口(interface)不仅仅是类型系统使用的抽象;它也是类型系统使用的抽象。相反,您可以拥有接口(interface)类型的值,这些值在内存中的表示形式是 struct
包含两个指针:一个指向描述变量中存储的值的动态类型的内部对象,另一个指向该值本身(或运行时在堆上创建的它的副本)。
这意味着,如果您要存储指向 sync.Map
中任何内容的指针,任何存储的此类指针都将被转换为 interface{}
类型的值。它会在 sync.Map
中占据完全相同的空间。 .
相反,如果您要存储 net.Conn
类型的值直接在那里,它们会被直接存储——只是因为它们已经是接口(interface)值,所以 Go 只会复制这对指针。
从表面上看,这两种方法在使用的空间方面似乎是相同的,但请耐心等待。
存储指向 net.Conn
的指针容器数据类型中的值,例如 sync.Map
,程序必须确保该值分配在堆上(而不是直接将其分配在当前运行的 goroutine 的堆栈上),这一事实可能会迫使编译器进行安排,以确保原始 net.Conn
值直接在堆上分配。
换句话说,存储指向接口(interface)类型变量的指针可能(并且通常会——由于典型代码的组织方式)在内存使用方面更加浪费。
此外,大多数解引用(指针追逐)往往会破坏 CPU 缓存;这不会改变游戏规则,但当您在紧密循环中迭代集合时,可能会增加几微秒的时间。
话虽如此,我建议不要彻底放弃将指针存储在像 sync.Map
这样的容器中。 :偶尔它会派上用场,例如,要重用 slice 数组,您通常存储指向此类数组的第一个元素的指针。
关于go - 按值或引用存储 net.Conn?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70754495/