每种语言都有“坑”,有些是语言设计问题,有些是使用者的问题。不管如何,了解这些东西后能让我们的代码更健壮,BUG更少。
资源泄漏
time.Tick()
func Tick(d Duration) <-chan Time
可用来做定时器,官网上的示例如下:
1 | c := time.Tick(1 * time.Minute) |
但使用此函数的话,没有办法释放底层的资源,看下此函数的说明:
Tick is a convenience wrapper for NewTicker providing access to the ticking channel only. While Tick is useful for clients that have no need to shut down the Ticker, be aware that without a way to shut it down the underlying Ticker cannot be recovered by the garbage collector; it “leaks”. Unlike NewTicker, Tick will return nil if d <= 0.
下面是验证的例子,会发现 tickLeak()
执行完后 CPU 依然占用很高:
1 | func tickLeak() { |
所以应该像上面注释中说的那样,只在不需要关闭 Ticker 时使用 time.Tick()。否则使用 time.NewTicker()
,并在不需要时主动调用 func (*Ticker) Stop
。
更新 map 中的值
map 中的值是结构体时,想要更新结构体的某一个字段:
1 | package main |
这段代码编译会报错:
1 | .\main.go:10: cannot assign to struct field m["first"].A in map |
要这样写:
1 | m["first"] = T{ |
或把 map 的值改成指针类型:
1 | m := make(map[string]*T) |
但要注意使用指针时,如果 map 中没有这个 key 的话会 panic:
1 | panic: runtime error: invalid memory address or nil pointer dereference |
make slice 时的参数
使用 make 创建 slice 时,可以指定 slice 的容量,减少 append 时重新分配内存的次数。但如果代码是这样写的,那就会引入BUG:
1 | package main |
上面代码会打印出:[0 0 0 1 2 3]
,因为 make 只指定了两个参数,这样 slice 的长度和容量都是 3,即 s 中已经有了三个值为 0 的元素。
rand.NewSource() 不是并发安全的
使用随机数时一般习惯用当前的时间戳做为种子写成这样 r := rand.New(rand.NewSource(time.Now().Unix()))
,之后再使用 r.Int()
等函数生成随机数,但这不是并发安全的,rand.NewSource()
的注释如下:
// NewSource returns a new pseudo-random Source seeded with the given value.
// Unlike the default Source used by top-level functions, this source is not
// safe for concurrent use by multiple goroutines.
如果直接使用 rand 包的随机数函数就没有问题,是因为 rand 包使用的全局变量定义成 var globalRand = New(&lockedSource{src: NewSource(1).(Source64)})
,lockedSource 是有锁的:
1 | type lockedSource struct { |