标题:Golang rand.Rand 并发panic: index out of range 出处:Felix021 时间:Tue, 02 Jul 2019 19:16:12 +0000 作者:felix021 地址:https://www.felix021.com/blog/read.php?2205 内容: 线上服务Panic,部分日志如下 引用 err: runtime error: index out of range Traceback: goroutine 19209941 [running]: ... panic(0x191d0e0, 0x2e078d0) /usr/local/go/src/runtime/panic.go:502 +0x229 math/rand.(*rngSource).Uint64(...) /usr/local/go/src/math/rand/rng.go:246 math/rand.(*rngSource).Int63(0xc438bb2a00, 0x0) /usr/local/go/src/math/rand/rng.go:231 +0x8a math/rand.(*Rand).Int63(0xc4279b3a70, 0x0) /usr/local/go/src/math/rand/rand.go:82 +0x33 math/rand.(*Rand).Int(0xc4279b3a70, 0x0) /usr/local/go/src/math/rand/rand.go:100 +0x2b ... 放狗搜了一下:math.Rand is not safe for concurrent use from: https://github.com/golang/go/issues/3611 这个 issue 的 4 楼还提到 "top-level functions like strings.Split or fmt.Printf or rand.Int63 may be called from any goroutine at any time" 翻了一下源码,rand.Int() 用是自带 lock 的 globalRand 对象 func Int() int { return globalRand.Int() } ... var globalRand = New(&lockedSource{src: NewSource(1).(Source64)}) ... type lockedSource struct { lk sync.Mutex src Source64 } ... func (r *lockedSource) Uint64() (n uint64) { r.lk.Lock() n = r.src.Uint64() r.lk.Unlock() return } 看了下调用代码,之前的实现为了避免多个 goroutine 竞争同一个锁,所以 new 了一个 rand.Rand 对象,但没考虑到这个对象不支持并发。 最终的解决方案,是实现了一个 safeRander 。 具体代码不适合贴,核心逻辑是初始化 N 个 rand.Rand 对象和对应的 N 个锁,以及一个 index,每次调用 Int() 时,先 atomic.AddUint32(&index, 1) % N,加上对应的锁,再用对应的 rand.Rand 对象。 这样只要并发使用的goroutine不超过N个,就不会出现竞争;就算超过,竞争出现的频率也大幅减少了,而且也可以通过增加 N 来优化。 Generated by Bo-blog 2.1.0