Appearance
高级篇
for select时,如果通道已经关闭会怎么样?如果select中只有一个case呢?
- select中如果任意某个通道有值可读时,它就会被执行,其他被忽略。
- 如果没有default字句,select将有可能阻塞,直到某个通道有值可以运行,所以select里最好有一个default,否则将有一直阻塞的风险。
go
const fmat = "2006-01-02 15:03:04"
func main() {
c := make(chan int)
go func() {
time.Sleep(1 * time.Second)
c <- 10
close(c)
}()
for {
select {
case x, ok := <-c:
fmt.Printf("time=%v,x=%v,ok=%v \r\n", time.Now().Format(fmat), x, ok)
time.Sleep(500 * time.Millisecond)
default:
fmt.Printf("time=%v,没有读到信息,进入default \r\n", time.Now().Format(fmat))
time.Sleep(500 * time.Millisecond)
}
}
}简单聊聊内存逃逸?
- 什么是内存逃逸:golang程序变量会携带有一组校验数据,用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验,它就可以在栈上分配。否则就说它"逃逸"了,必须在堆上分配。
- 常见情况:
- 在方法内把局部变量指针返回:局部变量原本应该在栈中分配,在栈中回收。但是由于返回时被外部引用,因此其生命周期大于栈,则溢出。
- 发送指针或带有指针的值到 channel 中:在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。
- 在一个切片上存储指针或带指针的值:一个典型的例子就是 []*string 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
- slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap ):slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
- 在 interface 类型上调用方法:在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。
- 怎么避免内存逃逸?
- go build -gcflags=-m 查看逃逸情况
go垃圾回收机制
- 引用计数算法
- 引用计数通过在对象上增加自己被引用的次数,被其他对象引用时加1,引用自己的对象被回收时减1,引用数为0的对象即为可以被回收的对象,这种算法在内存比较紧张和实时性比较高的系统中使用比较广泛,如php,Python等。
- 三色标记法
- 三色标记 + 混合写屏障
- 白色对象表示暂无对象引用的潜在垃圾,其内存可能会被垃圾收集器回收。
- 灰色对象表示活跃的对象,黑色到白色的中间状态。
- 黑色对象表示活跃的对象,包括不存在引用外部指针的对象以及从根对象可达的对象。
- 将所有对象标记为白色。
- 从根节点集合出发,将第一次遍历到的节点标记为灰色放入集合列表中。
- 遍历灰色集合,将灰色节点遍历到的白色节点标记为灰色,并把灰色节点标记为黑色。
- 循环这个过程。
- 直到灰色节点集合为空,回收所有的白色节点。
