
为什么 rand.Intn 在并发里总抽到同一个奖品?
因为默认的全局 rand.Rand 实例没做并发保护,而且它底层用的是未加锁的全局 src —— 多个 goroutine 同时调 rand.Intn,可能读到同一时刻的种子状态,尤其在高并发短时密集调用下,返回值高度重复。
常见错误现象:for i := 0; i 输出几乎全是相同数字。
- 别复用全局
rand,每个 goroutine 自己配一个独立rand.Rand - 用
time.Now().UnixNano()或crypto/rand生成真随机种子(测试可用时间戳,生产建议用crypto/rand) - 如果必须共享一个
rand.Rand,得手动加sync.Mutex,但会严重拖慢吞吐,不推荐
如何让抽奖结果既随机又可重现(比如回溯中奖记录)?
关键不是“去掉随机”,而是“控制随机源”——把种子固化下来,而不是依赖系统时间。
使用场景:抽奖活动需要审计、重放、压测或 AB 测试;你得确保同一组输入(如用户 ID + 活动 ID)总是产出相同中奖结果。
立即学习“go语言免费学习笔记(深入)”;
- 用
sha256.Sum256把业务标识(如"user_123:act_456")哈希成固定长度字节,再转为int64当种子 - 构造本地
rand.New(&rand.Source64{...}),避免污染全局状态 - 注意:不要直接用
hash.Hash.Write返回值当种子——它不是整数,得取哈希值前 8 字节再binary.BigEndian.Uint64
seed := binary.BigEndian.Uint64(hash[:8]) r := rand.New(rand.NewSource(int64(seed))) winner := r.Intn(len(prizes))
sync.WaitGroup 和 context.WithTimeout 怎么配合防抽奖卡死?
抽奖逻辑若含 HTTP 调用、数据库查库存、外部签名等,单个 goroutine 卡住会导致整个批次阻塞,WaitGroup 等不到 Done 就永远 hang 住。
性能影响:不设超时,1 个慢接口会让 1000 个并发抽奖全堵住;加上 context 后,超时 goroutine 自行退出,主流程仍能统计成功数。
-
WaitGroup只负责计数,不处理错误或超时,它和context是互补关系,不是替代关系 - 每个 goroutine 内部用
select { case 主动响应取消 - 别在
defer wg.Done()前加可能 panic 的操作,否则Done()永远不执行,主 goroutine 死等
为什么用 sync.Map 存中奖结果反而更慢?
因为 sync.Map 是为「读多写少 + 键分散」场景优化的,而抽奖结果通常是「短时高频写入 + 随后批量读」,这时候用普通 map 加一把 sync.RWMutex 更快、内存更省、逻辑更可控。
容易踩的坑:sync.Map.LoadOrStore 看似方便,但它每次都会对 key 做两次哈希、且内部有原子操作开销,在 10k+ 并发写入时比带锁普通 map 慢 3–5 倍。
- 写入阶段用
mutex.Lock(); m[key] = val; mutex.Unlock(),简单直接 - 读取阶段用
mutex.RLock(); defer mutex.RUnlock(),允许多读并发 - 如果真要无锁,考虑初始化时预分配好 map 容量(
make(map[string]Prize, 10000)),减少扩容冲突










