一、基础概念与工作原理
1.1 伪随机数的性质
在计算机编程里,随机数通常是伪随机的,由确定性算法根据一个种子生成。这里的种子是决定序列的起点,改变种子会产生完全不同的序列。
Go 语言的 math/rand 提供了两类入口:全局函数和 Rand 的实例。它们都依赖一个随机源,而该源的质量直接影响序列的统计分布。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano()) // 使用当前时间作为种子
fmt.Println(rand.Intn(100))
}
1.2 math/rand 的源与随机序列
通过 NewSource 和 New 可以创建一个独立的随机源,这样不同的区域不会互相干扰。对随机序列的重现性,只要在同一种子下重复运行,输出序列将完全相同。
要实现可重复的测试,推荐使用 rand.NewSource(seed) 搭配 rand.New,得到一个独立的 Rand 实例。
二、从种子到随机序列:如何设置种子
2.1 全局源与自定义 Rand
使用全局函数时,通常 Seed 会设定一次,随后调用 Intn、Float64 等得到随机值。对于并发场景,独立的 Rand 实例能降低竞争。
如果你需要在同一进程中保持不同随机序列,请创建 独立的 Rand 实例,避免全局源被同频访问造成的相关性降低。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// 全局源示例
rand.Seed(time.Now().UnixNano())
fmt.Println("global:", rand.Intn(100))
// 自定义源示例
src := rand.NewSource(time.Now().UnixNano())
r := rand.New(src)
fmt.Println("custom:", r.Intn(100))
}
2.2 使用时间作为种子与改进随机性
通常采用 time.Now().UnixNano() 作为 种子,以确保每次程序执行获取到不同的序列。若你需要可重复的测试,请记录下该种子值并在未来重复使用。
在进一步的场景中,可以通过读取系统中的 密码学随机源来初始化种子,以减少对可预测性的影响。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed))
fmt.Println(r.Intn(100))
}
三、在指定区间内生成随机数的实战
3.1 生成指定区间的整数
要在区间 [min, max] 内生成随机整数,常用的方法是 Intn 与偏移组合。关键点在于区间长度 max-min+1。
另一种思路是对全局的 Float64 范围进行线性变换,但通常直接使用 Intn 更简洁且避免溢出。
package main
import (
"fmt"
"math/rand"
"time"
)
func randIntInRange(r *rand.Rand, min, max int) int {
if max <= min {
return min
}
return r.Intn(max-min+1) + min // 包含 min 和 max
}
func main() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
fmt.Println(randIntInRange(r, 10, 20))
}
3.2 生成指定区间的浮点数与分布控制
浮点数区间通常通过对 Float64 的线性变换实现,即 min + (max-min) * rand.Float64()。这使得区间边界的覆盖变得容易理解。
为了控制分布的偏斜(如正态分布近似),可以结合其他分布库或手工实现。线性变换是最直接的方案,也是工程中最常用的做法。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
min, max := 0.0, 1.0
v := min + (max-min)*r.Float64()
fmt.Println(v)
// 生成区间 [5.0, 9.0)
min2, max2 := 5.0, 9.0
v2 := min2 + (max2-min2)*r.Float64()
fmt.Println(v2)
}
四、进阶用法与注意事项
4.1 并发场景下的随机数生成
在高并发场景中,全局源 的并发访问可能成为瓶颈,因此更推荐为每个工作单元创建 独立的 Rand 实例,以避免锁竞争。
另外,并发安全 的前提是尽量避免跨 goroutine 共享同一个随机源。使用 goroutine 本地的 Rand,可以提升吞吐量并保持确定性。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func worker(id int, ch chan<- int) {
r := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id)))
ch <- r.Intn(100)
}
func main() {
const workers = 4
ch := make(chan int, workers)
var wg sync.WaitGroup
wg.Add(workers)
for i := 0; i < workers; i++ {
go func(id int) {
defer wg.Done()
worker(id, ch)
}(i)
}
wg.Wait()
close(ch)
for v := range ch {
fmt.Println(v)
}
}
4.2 避免常见偏差和坑
常见坑包括:把 Intn 的参数写错成 max 而不是区间长度;或忘记对 种子进行初始化造成结果不可预测。
要避免偏差,务必确保区间长度正确且种子正确初始化。区间长度的计算要清晰且测试覆盖。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// 正确写法:区间 [a, b] 的长度为 b-a+1
a, b := 3, 7
x := r.Intn(b-a+1) + a
fmt.Println(x)
} 

