Go 存在的坑

for range 不为人知的坑,其实以前用 php 的时候遇到过,Java 没有这个问题

// 因为 range 的时候,会创建一个临时变量 v 来接受 data 的遍历值
// 而不是你想的 append(&data[0], $data[1], ...)
data := []int{1,2,3}
result := []*int{}
for _, v := range data {
   result = append(result, &v)
   fmt.Println(&v) // 0xc0000ac008,0xc0000ac008,0xc0000ac008
}

for _, v := range result {
   fmt.Println(*v)  // [3,3,3]
}

// 正确处理方式
data := []int{1,2,3}
result := []*int{}
for i, v := range data {
   result = append(result, &data[i])
}

for _, v := range result {
   fmt.Println(*v)
}

结构体还是指针?

项目中函数返回值更多用的是结构体,而不是结构体指针。虽然这会提高内存拷贝开销,但另一方面可以降低 gc 压力。因为返回值如果用指针,那么变量会存储到堆,而不是栈,也就是发生了变量逃逸。

变量逃逸情况:
(1)函数中 new 或者字面量创建出来的变量,如果取变量指针作为函数返回值,那么该变量发生逃逸
(2)逃逸变量引用的指针逃逸
(3)被指针类型的 slice,map,chan 饮用的变量发生逃逸

空结构体

在不关心值的内容时,使用空结构体 struct{}{},因为不占内存空间。空结构体其实取得都是 Go 特殊变量 zerobase 的地址

Go 内存管理

内存管理三大组件:线程缓存(mcache)、中心缓存(mcentral)、页堆(mheap)。微对象和小对象会依次尝试使用三大组件进行内存分配,大对象直接在堆上分配。
Go内存管理

Go Map

(1)Go map 以桶作为一层结构,查找元素时直接定位到桶,发生哈希冲突时,再进入桶的下一层结构 cell,遍历 cell 查找目标值。桶结构类似于 java map 的 entry 结构,cell 的作用就像 entry 构成的链表,用来解决哈希冲突。此外,当冲突太多时同样通过扩容来提高查询效率,扩容不是一次性完成的特点又与 redis 哈希的渐进式扩容方式有异曲同工之妙

(2)Map 的键必须是可比较类型,不能是切片,map,函数,且使用 float 类型作为键时需注意,值可以是任意类型

(3)Map 非线程安全,只支持并发读;map for range 遍历时的顺序是随机的!

方法接受者的判断

总结而言就是,在不需要修改接受者,且接受者体量不会很大的情况下,使用值接受者,其他情况都使用指针接受者。另外,当结构体存在锁变量时,也要使用指针接受者,避免锁拷贝

Go 倾向于无法使用值类型的情况下,才使用指针类型,因为指针类型可能会有并发问题,并且值类型也不是性能瓶颈

Context 理解

Context 只读,可不断生成新的 context 变量,形成类似树状关系的结构;context 功能在于存储只读 key-value 和进行协程间的通知

Defer 函数

Defer 在函数返回前调用,可在其内进行资源回收,注意直接调用资源回收由于提前返回或者异常导致没有执行


那个少年
4 声望0 粉丝

« 上一篇
shell 基础
下一篇 »
通用术语指北