今天再看 timer
源码的时候,在函数 clearDeletedTimers() 里看到一段对切片的处理代码,实现目的就是对一个切片内容进行缩容。
// src/runtime/time.go
// The caller must have locked the timers for pp.
func clearDeletedTimers(pp *p) {
timers := pp.timers
......
// 对无用的切片元素赋值 nil
for i := to; i < len(timers); i++ {
timers[i] = nil
}
atomic.Xadd(&pp.deletedTimers, -cdel)
atomic.Xadd(&pp.numTimers, -cdel)
atomic.Xadd(&pp.adjustTimers, -cearlier)
timers = timers[:to]
pp.timers = timers
updateTimer0When(pp)
......
}
to
变量指新切片的长度, len(timers)
指原来切片的长度。
这里在其进行 timers = timers[:to]
操作前,先是将 to 数组索引后的值进行了赋值 nil
。按照我们平时用用法,是没有必要执行这一步的?那为什么这里要加这一步呢,其实这里与GC 有关?
在日常开发中很少注意到这个细节,虽然最终实现的结果是一样的,但如果考虑GC的话,差别可就大多了。
假如这里不进行赋值nil 的话,结果是没有问题的,但我们知道slice是底层是由三部分组成的。
type slice struct {
array unsafe.Pointer
len int
cap int
}
slice.array
字段是一个指向底层数组的指针,这里函数的参数 pp
是指 GPM 中的 P 结构体,pp.timers
字段类型为 timers []*timer
。
一个应用程序会长时间处于运行状态,当GC执行的时候,会发现 pp.timers
字段是slice类型,其值类型是一个指针类型,这个切片变量(数组)会一直存在于整个应用程序的生命周期。如果不先赋值 nil
的话,GC
扫描的时候,会发现这些指针元素
仍处于被切片引用状态,这样就导致一直占用内存(已无用的数据);如果先赋值的话,则在GC在扫描对象时,会发现指针元素已经没有任何对象在引用,就会立即被标记并清除(三色标记清除法),回收这些指针对象的内存。
总结
在了解这种用法前,很有必要了解当前应用的执行上下文及环境,如果只是一个临时变量且生命周期很短的话,就没有必要这样做了,如一个函数的栈变量。这里的环境P是长期处于运行状态中的指针变量,所以很有必要进行手动赋值为nil
, 等待对象被GC回收。
有疑问加站长微信联系(非本文作者)