Golang初级实战:实现一个简单的并发抽奖程序 Go语言随机性与同步

2次阅读

Golang初级实战:实现一个简单的并发抽奖程序 Go语言随机性与同步

为什么 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.WaitGroupcontext.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)),减少扩容冲突

实际跑起来最麻烦的从来不是并发模型,而是“谁来保证库存不超发”和“怎么让同一个用户不能重复中奖”——这两条约束一旦漏掉,再多的随机和同步都救不回来。

text=ZqhQzanResources