游戏开发过程中,有大量的随机数需求。本文将着重介绍go的随机数用法(伪随机)。
原理:
种子决定了接下来的随机数值,直到种子变化!
相同种子的情况下得到的随机数值是必然一样的。
一旦错误的使用了种子,为什么会出现连续出现相同数的情况。在rand包中默认会有一个种子数为0的随机数生成器。首先看一下下面的代码。
错误用法一:
for i:=0;i<10;i++{
rand.Seed(time.Now().UnixNano())
x:=rand.Int()
}
上面的错误使用例子中,x很大程度上将是相同的数。因连续10次设置了相同的种子。开头原理部分已经知道为何会得到这样的结果,下面通过另外一个方式再描述:
当我们调用rand.Seed(种子)之后,立即就会得到一个将来随机出来的数组。
[数1、数2、数3、...数n]
每当调用Seed后,根据种子不同,数组不同。并且会将下一次取出的随机数下表设置到0。
我们改进用法如下:(错误用法二)
rand.Seed(time.Now().UnixNano())
for i:=0;i<10;i++{
x:=rand.Int()
}
上面代码中,我们将得到连续10个随机数,且他们都是有效的。但这依然是错误的用法。因为在并发情况下多处调用rand.Seed实际上设置的是同一个生成器的种子(这样必然可能导致反复设置到相同的种子情况存在)。下面是正确创建一个随机数生成器的代码。
rand.New(rand.NewSource(种子))
将错误方法二中的rand.替换为手动创建的独立生成器即可根治该问题。
实际生产举例:
客户端计算,服务端验证的情况下,客户端保持后后端同样的种子算法。在验证时提交使用的种子,服务端使用相同的种子计算(则可以完整还原客户端环境下的所有数值情况)
下面是封装之后的随机数函数(在全局非验证情况下,可以直接调用Rand函数,无需担心种子问题)
package util
import (
"math/rand"
"sync/atomic"
"time"
)
//@ return nil
func NewRand(seed int64) *rand.Rand {
return rand.New(rand.NewSource(seed))
}
var _seedSec int64 = 0
func seed() {
s := time.Now().Unix()
old := atomic.SwapInt64(&_seedSec, s)
if old != s { //对比先后时间,若超过1秒,重新用纳秒重置种子
rand.Seed(time.Now().UnixNano())
}
}
//@return 0
func RandInt() int {
seed()
return rand.Int()
}
//@return 0
func RandIntn(n int) int {
seed()
return rand.Intn(n)
}
//@return 0
func RandInt31() int32 {
seed()
return rand.Int31()
}
//@return 0
func RandInt31n(n int32) int32 {
seed()
return rand.Int31n(n)
}
func RandInt63() int64 {
seed()
return rand.Int63()
}
//@return 0
func RandInt63n(n int64) int64 {
seed()
return rand.Int63n(n)
}