不调用seed或等效初始化时,rand.intn等函数因默认种子为1而返回固定序列(如两次intn(100)和为168),非bug而是确定性设计;go 1.20+应使用rand.new(rand.newsource(time.now().unixnano()))创建独立实例,避免全局状态与并发问题。

不调用 Seed 或等效初始化,rand.Intn 等函数永远返回固定序列——比如你看到的 168,就是 seed=1 时前两个 Intn(100) 的和(68 + 100),不是 bug,是确定性行为。
为什么 rand.Seed(time.Now().UnixNano()) 必须放在 main 开头?
Go 的包级变量初始化发生在 main() 执行前,此时 time.Now() 不可用,且 rand.Intn 等顶层函数依赖的全局源(default Source)尚未被修改。如果你写成:
var a = rand.Intn(100) // ❌ 错误:此时 seed 还是默认的 1
它会在 import 阶段就求值,结果恒定。正确姿势只有:
- 在
main()函数第一行调用rand.Seed(time.Now().UnixNano()) - 或更推荐:用
rand.New(rand.NewSource(...))构造独立实例,完全绕过全局状态 - 绝对不要在 init() 或包级变量中调用
time.Now()—— 编译会报错或行为未定义
Go 1.20+ 应该用 rand.New 还是 rand.Seed?
rand.Seed 在 Go 1.20 已被标记为 deprecated,官方明确建议迁移到实例化方式。原因很实际:
-
rand.Seed修改全局共享源,多 goroutine 并发调用rand.Intn可能引发竞态(虽文档说“safe for concurrent use”,但实际仍共享状态) -
rand.New创建的是独立*rand.Rand实例,天然线程安全,也方便注入测试种子 - 示例:
r := rand.New(rand.NewSource(time.Now().UnixNano())),之后全用r.Intn(100)
UnixNano() 比 Unix() 更可靠,但也不是万能
纳秒级时间戳极大降低了种子重复概率,但在以下场景仍可能撞车:
- 容器冷启动后快速连续运行多个进程(如 CI 流水线里并行跑多个 test binary)
- 嵌入式设备或虚拟机中系统时钟精度不足,
UnixNano()返回值实际不变 - 单元测试中需要可重现结果——这时反而要传固定 seed,比如
rand.NewSource(42)
真正高要求的场景(如加密、抽奖),不应依赖 math/rand,而应改用 crypto/rand 读取操作系统熵池。
最容易被忽略的一点:哪怕你用了 rand.New,如果忘记传 rand.NewSource(...),而是直接 rand.New(nil),它会 fallback 到默认 seed=1 的源——结果还是 168。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











