不能——sync.mutex 默认不安全,需确保锁为结构体字段、覆盖全部临界区、defer unlock紧贴lock;atomic仅保单变量原子性,不保多步逻辑一致性;测试需校验id唯一性而非仅总数。

Go 里用 sync.Mutex 保护 Sequence 生成器,真能防住并发冲突?
不能——至少不是默认就安全。很多人以为加个 sync.Mutex 就万事大吉,结果压测时还是出现重复 ID。问题常出在:锁没覆盖全部临界区、defer mu.Unlock() 被提前 return 绕过、或误把锁对象定义在方法内(每次调用都是新锁)。
正确做法是把 sync.Mutex 作为结构体字段,且所有读写 next 的路径都必须走同一把锁:
type Sequence struct {
mu sync.Mutex
next int64
}
func (s *Sequence) Next() int64 {
s.mu.Lock()
defer s.mu.Unlock()
s.next++
return s.next
}
- 别在
Next()里 new 一个sync.Mutex,否则锁失效 - 如果生成逻辑包含 IO 或可能 panic,确保
Unlock()在defer中且位置紧贴Lock() - 注意:
sync.Mutex不可复制,结构体不能被值传递(比如传参时写func f(s Sequence))
为什么 atomic.AddInt64 比加锁更快,但有时还是出错?
因为原子操作只保单个变量的读-改-写线程安全,不保业务逻辑完整性。比如你想“取当前值 → 做校验 → 再自增”,这三步之间有竞态窗口,atomic 无法帮你锁住整个流程。
常见翻车场景:
立即学习“go语言免费学习笔记(深入)”;
- 用
atomic.LoadInt64(&s.next)判断是否超限,再用atomic.AddInt64(&s.next, 1)—— 中间可能已被其他 goroutine 修改 - 多个字段需同步更新(如
next和version),atomic无法跨字段保证一致性 -
atomic对非对齐内存(如 struct 里混排的字段)行为未定义,某些架构下会 panic
示例(错误):
if atomic.LoadInt64(&s.next) > 1e6 {
return errors.New("exhausted")
}
return atomic.AddInt64(&s.next, 1) // 这里可能已超限
测试高并发 Sequence 冲突,光靠 go test -race 不够
-race 能抓数据竞争,但抓不到逻辑冲突——比如两个 goroutine 都拿到相同 next 值,这是正确执行的结果(没数据竞争),但业务上非法。
注意:请在linux环境下测试或生产使用 青鸟内测是一个移动应用分发系统,支持安卓苹果应用上传与下载,并且还能快捷封装网址为应用。应用内测分发:一键上传APP应用包,自动生成下载链接和二维码,方便用户内测下载。应用封装:一键即可生成app,无需写代码,可视化编辑、 直接拖拽组件制作页面的高效平台。工具箱:安卓证书生成、提取UDID、Plist文件在线制作、IOS封装、APP图标在线制作APP分发:
必须自己构造确定性可验证的测试:
- 启动 N 个 goroutine 并发调用
Next()M 次,收集全部返回值 - 用
map[int64]bool检查是否有重复,而非只看总数是否等于N*M - 加
runtime.Gosched()或短 sleep 提高调度扰动,更容易暴露时序问题 - 避免用
time.Sleep等待 goroutine 结束,改用sync.WaitGroup
关键代码片段:
var wg sync.WaitGroup
seen := make(map[int64]bool)
mu := sync.RWMutex{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
id := seq.Next()
mu.Lock()
if seen[id] {
t.Errorf("duplicate id: %d", id)
}
seen[id] = true
mu.Unlock()
}
}()
}
wg.Wait()
Redis 或 DB 做 Sequence 后端时,本地缓存怎么不放大冲突风险?
本地缓存(比如预取 1000 个号存内存)本身没错,但容易忽略两个关键点:缓存耗尽时的获取逻辑、以及多实例部署下的全局唯一性保障。
典型坑点:
- 缓存为空时直接调 Redis
INCR,但没设超时或重试,网络抖动导致多次请求都 fallback 到 DB,DB 又没加行锁,结果生成重复起始号 - 多个服务实例各自缓存一批号,但没做号段隔离(如实例 A 拿 [1000-1999],B 拿 [2000-2999]),最终还是撞
- 用
redis.Incr()时没处理redis.Nil(key 不存在),导致反复初始化为 0
建议策略:号段 + 原子预分配。例如用 Redis INCRBY 一次性拿走 1000 个号,再本地递增分发:
start := redis.IncrBy(ctx, "seq:order", 1000).Val() // 返回旧值 // 本实例可用范围:[start+1, start+1000]
这样既减少网络往返,又靠 Redis 单点保证号段不重叠。
真正难的从来不是“怎么实现 Sequence”,而是“怎么证明它在 5000 QPS 下连续跑一周不重复”。边界条件、实例扩缩、时钟回拨、节点宕机恢复……这些地方一漏,压测时看着绿,上线后半夜告警。









