math/rand需显式seed(go 1.20+用rand.new(rand.newsource(time.now().unixnano()))),crypto/rand用于密码学场景,二者不可混用,且并发时math/rand需同步保护而crypto/rand天然安全。

math/rand 生成随机数前必须 seed,否则每次运行结果一样
Go 的 math/rand 默认使用固定种子,不显式设置的话,Rand.Intn(100) 每次跑都是同一串数字。这不是 bug,是设计——它让程序可复现,但对“随机”场景就是坑。
- 用
rand.Seed(time.Now().UnixNano())初始化(Go 1.20+ 已弃用,见下一条) - Go 1.20+ 正确写法是:声明一个全局
var r = rand.New(rand.NewSource(time.Now().UnixNano())),然后调r.Intn(100) - 别在 goroutine 里反复 new
rand.New,开销小但没必要;共享一个实例更安全 - 如果需要多个独立随机流(比如并发生成不同序列),才为每个 goroutine 单独 new 一个
rand.Rand
crypto/rand 适合生成密钥、token,不能用 math/rand 替代
crypto/rand 从操作系统熵池读取真随机字节,math/rand 是伪随机——后者速度块、可预测,前者慢但安全。选错会导致 token 可被暴力猜出。
- 生成 16 字节随机 key:
key := make([]byte, 16); _, err := rand.Read(key) - 错误写法:
rand.Intn(256)循环拼 byte —— 这不是密码学安全的 - 注意
rand.Read返回err,虽然 Linux/macOS 下极少失败,但 Windows 或容器环境可能返回io.EOF,得检查 - 别用
crypto/rand做游戏骰子或 shuffle 列表——性能差,且没必要
生成随机字符串时,字符集和长度要自己控制,别硬编码 0-9a-z
直接用 rand.Intn(len(chars)) 查表最稳,别依赖 fmt.Sprintf("%x", rand.Uint64()) 这类间接方式——它长度不固定、字符集受限、还可能含 0。
- 常用字符集定义:
const letters = "abcdefghijklmnopqrstuvwxyz0123456789" - 生成 10 位字符串:
for i := range b { b[i] = letters[rand.Intn(len(letters))] } - 如果需要 URL 安全 base64 随机串,用
base64.RawURLEncoding.EncodeToString包装crypto/rand生成的字节,别手搓 - 避免用
strings.Builder+strconv.Itoa拼接数字再取模——逻辑绕、易溢出、字符分布不均
并发环境下 rand.Rand 不是线程安全的,但 crypto/rand 是
math/rand 的方法(如 Intn)内部修改状态,多个 goroutine 同时调用会 panic 或返回错误值;而 crypto/rand.Read 是无状态系统调用,天生并发安全。
- 共享一个
rand.Rand实例时,必须加sync.Mutex或改用rand.New(&lockedSource{})(不推荐) - 更简单做法:每个 goroutine 自己 new 一个
rand.Rand,用不同 seed(比如time.Now().UnixNano() + int64(goroutineID)) - 如果只是生成 session ID 或验证码,优先用
crypto/rand,省去锁和 seed 管理 - benchmark 显示:高并发下
crypto/rand比加锁的math/rand还快,因为系统调用批处理优化得好
真正难的不是调哪个函数,而是想清楚这个随机值用在哪儿——是给用户看的 ID,还是加密盐值,或是测试用的假数据。用错地方比写错代码更难 debug。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











