Go语言rand.Intn默认返回固定序列(如168)是设计特性而非bug;Go 1.20+必须弃用rand.Seed,改用rand.New(rand.NewSource(time.Now().UnixNano()))创建独立实例,避免全局状态污染和并发不安全。

Go 语言中不初始化随机种子,rand.Intn 等函数永远返回固定序列——比如你看到的 168,不是 bug,是确定性行为;Go 1.20+ 应直接弃用 rand.Seed,改用 rand.New(rand.NewSource(...))。
为什么 rand.Seed(time.Now().UnixNano()) 必须写在 main 第一行?
包级变量(如 var a = rand.Intn(100))在 main() 执行前就完成初始化,此时 rand.Seed 还没被调用,全局源仍是默认 Seed(1),结果恒定。而 time.Now() 在包初始化阶段不可用,强行放进去会编译失败或行为未定义。
-
rand.Seed只影响后续调用,对它之前发生的rand.Intn无效 - 不要在
init()函数里调用time.Now()初始化种子——Go 不允许在 init 阶段做这种运行时求值 - 哪怕只写一次
rand.Seed,也必须确保它出现在所有rand.*调用之前
Go 1.20+ 为什么必须用 rand.New 而不是 rand.Seed?
rand.Seed 已被官方标记为 deprecated,核心问题是它修改的是全局共享源:rand.Intn、rand.Float64 等顶层函数都共用同一个 default Source,多 goroutine 并发调用时虽不会 panic,但可能因状态竞争导致输出可预测性下降或性能抖动。
-
r := rand.New(rand.NewSource(time.Now().UnixNano()))创建的是独立实例,天然线程安全 - 测试时可轻松注入固定种子:
rand.New(rand.NewSource(42)),保证可重现 - 若误写成
rand.New(nil),它会 fallback 到默认Seed(1)源——又回到168问题
UnixNano() 真的够用吗?哪些场景会失效?
time.Now().UnixNano() 是目前最常用且推荐的种子来源,但不是万能解。纳秒级时间戳极大降低重复概率,但在以下情况仍可能撞车:
立即学习“go语言免费学习笔记(深入)”;
- CI 流水线中多个 test binary 并行启动,容器冷启动后系统时钟尚未稳定,多次调用返回相同纳秒值
- 嵌入式设备或某些虚拟机中,
UnixNano()实际精度只有毫秒甚至秒级 - 单元测试需要确定性输出时,反而要禁用时间种子,显式传入固定值(如
42)
真正高要求场景(如抽奖、token 生成),math/rand 本身就不该用——换 crypto/rand 读操作系统熵池。
rand.Intn(n) 的区间和常见误用
rand.Intn(n) 返回的是 [0, n) 区间整数(左闭右开),这点极易踩坑:
-
rand.Intn(1)永远返回0;要模拟抛硬币(0 或 1),必须写rand.Intn(2) - 想生成
[a, b]闭区间整数,得写a + r.Intn(b-a+1),不是r.Intn(b-a) - 高频循环中反复调用
time.Now().UnixNano()初始化新rand.Rand实例,可能因时间未变导致种子重复——应复用一个实例,而非每次重造
最容易被忽略的一点:无论你用 rand.New 还是 rand.Seed,只要种子没变、或根本没设(比如传了 nil),结果就还是那个 168。


















