返回
Featured image of post rand

rand

Golang rand包源码分析

源码剖析

通过日常随机数的代码使用,看下rank包具体的底层实现:

r := rand.New(rand.NewSource(time.Now().UnixNano())) // 初始化随机器
r.Intn(314)	// 生成 [0, 314) 内的整数

r := rand.New(rand.NewSource(time.Now().UnixNano())) 开始。

初始化 Rand 的时候,通过rand.New(rand.NewSource(seed))创建,所以看下rand.New()的实现。

// New returns a new Rand that uses random values from src
// to generate other random values.
func New(src Source) *Rand {
	s64, _ := src.(Source64)
	return &Rand{src: src, s64: s64}
}

可以看到 Rand 使用的是rand.NewSource()传入的 Source,因此接着看下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.
// The returned Source implements Source64.
func NewSource(seed int64) Source {
	return newSource(seed)
}

func newSource(seed int64) *rngSource {
	var rng rngSource
	rng.Seed(seed)
	return &rng
}

看到 Source 的实际类型是 rngSource,实现了接口 Source

type rngSource struct {
	tap  int           // index into vec
	feed int           // index into vec
	vec  [rngLen]int64 // current feedback register
}

看下接口 Source的定义。

// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
//
// A Source is not safe for concurrent use by multiple goroutines.
type Source interface {
	Int63() int64
	Seed(seed int64)
}

从 newSource 中,可以看到调用了 rng.Seed(seed) ,即 rngSource 的 Seed ,这个就是随机器初始化的核心函数。

🫵😎
const (
	rngLen   = 607
	rngTap   = 273
	rngMax   = 1 << 63
	rngMask  = rngMax - 1
	int32max = (1 << 31) - 1
)

// Seed uses the provided seed value to initialize the generator to a deterministic state.
func (rng *rngSource) Seed(seed int64) {
	rng.tap = 0
	rng.feed = rngLen - rngTap

	seed = seed % int32max
	if seed < 0 {
		seed += int32max
	}
	if seed == 0 {
		seed = 89482311
	}

	x := int32(seed)
	for i := -20; i < rngLen; i++ {
		x = seedrand(x)
		if i >= 0 {
			var u int64
			u = int64(x) << 40
			x = seedrand(x)
			u ^= int64(x) << 20
			x = seedrand(x)
			u ^= int64(x)
			u ^= rngCooked[i]
			rng.vec[i] = u
		}
	}
}


// seed rng x[n+1] = 48271 * x[n] mod (2**31 - 1)
func seedrand(x int32) int32 {
	const (
		A = 48271
		Q = 44488
		R = 3399
	)

	hi := x / Q
	lo := x % Q
	x = A*lo - R*hi
	if x < 0 {
		x += int32max
	}
	return x
}

通过逻辑可以看出,调用 rand.Seed 来设置种子, 其实就是给 rng.vec607 个槽设置对应的值。通过上面的源码那可以看出来, rand.Seed 会调用一个 seedrand 的函数, 来计算对应槽的值。这个函数的计算结果并不是随机的,而是根据 seed 实际算出来的,另外这个函数并不是随便写的,是有相关的数学证明的。 这也导致了相同的 seed,最终设置到 rng.vec 里面的值是相同的。 总结下,随机器初始化的逻辑其实就是为了进行 rng.taprng.feedrng.vec 的初始化工作。 其中,需要特别注意的是: 相同的seed种子 或者 seed种子取模int32最大值后相等; 这两种情况都会导致最终设置到 rng.vec 里面的值是相同的。


以上就是随机器初始化,接下来看下随机数是如何产生的。 r.Intn(314) 开始解析。rand.Intn() 为例,看下具体的实现。

// Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n).
// It panics if n <= 0.
func (r *Rand) Intn(n int) int {
	if n <= 0 {
		panic("invalid argument to Intn")
	}
	if n <= 1<<31-1 {
		return int(r.Int31n(int32(n)))
	}
	return int(r.Int63n(int64(n)))
}

查看源码可了解到,小于等于 MaxInt32 的值调用的是 r.Int31n

// Int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n).
// It panics if n <= 0.
func (r *Rand) Int31n(n int32) int32 {
	if n <= 0 {
		panic("invalid argument to Int31n")
	}
	if n&(n-1) == 0 { // n is power of two, can mask
		return r.Int31() & (n - 1)
	}
	max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
	v := r.Int31()
	for v > max {
		v = r.Int31()
	}
	return v % n
}

如果传入的值是2的幂次方,则调用的 r.Int31() & (n - 1)

因为传入的314不是2的幂次方,所以走的是 max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) 下面的逻辑。

针对 max 这个逻辑,需要理解它的用途,它其实是将 int32 0, (1 << 31) - 1 范围内尾部,“取模后”,不能覆盖 [0, n) 的数值去掉,这样就保证了 [0, n) 内各个数值出现的概率是一致的。举例如下:

var n int32 = 314
tail := (1 << 31) % uint32(n)
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
fmt.Println(tail)    // 282
fmt.Println(max)     // 2147483365
fmt.Println(max % n) // 313

继续往下看逻辑,无论是不是2的幂次方,最终都会调用到 r.Int31() ,所以需要看 r.Int31() 的具体实现。

// Int31 returns a non-negative pseudo-random 31-bit integer as an int32.
func (r *Rand) Int31() int32 { return int32(r.Int63() >> 32) }

r.Int31() 中调用的是 r.Int63() ,然后取结果的高31位作为 int32 随机值,下面看 r.Int63() 的实现。

// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
func (r *Rand) Int63() int64 { return r.src.Int63() }

可以看到,最终调用的是 r.src.Int63()

看下Rand的定义,因为上面已经分析出了Rand的src的具体实现就是 rngSource

// A Rand is a source of random numbers.
type Rand struct {
	src Source
	s64 Source64 // non-nil if src is source64

	// readVal contains remainder of 63-bit integer used for bytes
	// generation during most recent Read call.
	// It is saved so next Read call can start where the previous
	// one finished.
	readVal int64
	// readPos indicates the number of low-order bytes of readVal
	// that are still valid.
	readPos int8
}

因此,随机数的产生调用的就是 rngSource 结构体的 Int63 函数。

// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
func (rng *rngSource) Int63() int64 {
	return int64(rng.Uint64() & rngMask)
}

其中 rngMask 表示 Int64 的最大值,作用是作为掩码。

rngMax   = 1 << 63
rngMask  = rngMax - 1

rng.Uint64() ,则是产生随机数的核心函数;它的核心逻辑就是从数组 rngSource.vec 中取出随机数。

// Uint64 returns a non-negative pseudo-random 64-bit integer as an uint64.
func (rng *rngSource) Uint64() uint64 {
	rng.tap--
	if rng.tap < 0 {
		rng.tap += rngLen
	}

	rng.feed--
	if rng.feed < 0 {
		rng.feed += rngLen
	}

	x := rng.vec[rng.feed] + rng.vec[rng.tap]
	rng.vec[rng.feed] = x
	return uint64(x)
}

实际上,调用 Intn(), Int31n(), Int63(), Int63n() 等其他随机函数,最终调用到都是函数 rngSource.Uint64()可以看到每次调用就是利用 rng.feed, rng.tap 从 rng.vec 中取到两个值相加的结果返回,同时这个结果又重新放入 rng.vec。这样做的目的是,让随机数更加丰富随机,而不是仅局限于 rng.vec 数组中的值。

源码总结

通过以上的源码分析,可以总结出以下几点:

  1. rand.New 初始化出来的 rand 不是并发安全的。 因为每次利用 rng.feed, rng.tap 从 rng.vec 中取到随机值后会将随机值重新放入 rng.vec,当多 goroutine 同时调用时就会有数据竞争问题。如果想并发安全,可以使用全局的随机数发生器 rand.globalRand,它是基于lockedSource实现的,进行了加锁的逻辑。
  2. 相同seed种子,每次运行的结果是一样的。 因为随机数是从 rng.vec 数组中取出来的,这个数组是根据种子生成的,相同的种子生成的 rng.vec 数组是相同的。
  3. 不同seed种子,每次运行的结果可能一样。 因为根据种子生成 rng.vec 数组时会有一个对int32最大值取模的操作,模后的结果可能相同,也就是两个种子如果相差int32的最大值,就会导致 rng.vec 数组相同。
r1 := rand.New(rand.NewSource(1111))
r2 := rand.New(rand.NewSource(1111 + math.MaxInt32))
fmt.Println("第一个随机器:", r1.Intn(10000))  // 第一个随机器: 9241
fmt.Println("第一个随机器:", r1.Intn(10000))  // 第一个随机器: 8842
fmt.Println("第一个随机器:", r1.Intn(10000))  // 第一个随机器: 2221

fmt.Println("第二个随机器:", r2.Intn(10000))  // 第二个随机器: 9241
fmt.Println("第二个随机器:", r2.Intn(10000))  // 第二个随机器: 8842
fmt.Println("第二个随机器:", r2.Intn(10000))  // 第二个随机器: 2221
This sentence is false.
使用 Hugo 构建
主题 StackJimmy 设计