如何仅用Go原子操作实现无锁循环自增索引且避免竞态条件?
仅用Go原子操作实现带重置的自增逻辑
当然可以!完全不用锁,只靠Go的原子操作就能搞定这个需求。先说说你原来的示例代码为啥会有竞态问题:
你写的代码里,atomic.AddUint32(&index, 1)执行完之后,到atomic.CompareAndSwapUint32(&index, Max, 0)这一步之间,其他goroutine可能已经偷偷修改了index的值,导致CAS的判断条件失效,最后逻辑就乱了。
正确的原子实现方式
我们需要用CAS循环(Compare-And-Swap循环)来保证整个逻辑的原子性——毕竟单个原子操作没法完成“自增+判断重置”这一串动作,得通过循环尝试直到整个操作成功。
代码实现如下:
import "sync/atomic" const Max = 8 var index uint32 // 改用uint32适配原子操作的类型要求 func add() uint32 { for { // 先原子加载当前的index值,确保拿到的是最新状态 current := atomic.LoadUint32(&index) // 计算下一个值:自增后如果达到Max就重置为0 next := current + 1 if next >= Max { next = 0 } // 尝试CAS操作:如果当前index还是我们读取的current,就替换成next if atomic.CompareAndSwapUint32(&index, current, next) { // CAS成功,说明整个操作原子完成,返回新值 return next } // CAS失败,说明有其他goroutine修改了index,重新循环重试就行 } }
为啥这个实现是并发安全的?
- 整个逻辑通过CAS循环把“读当前值→算新值→替换新值”绑定成了一个原子操作:只有当
index的值和我们刚读取的current完全一致时,才会执行替换,避免了中间被其他goroutine篡改的可能。 - 如果期间有其他goroutine修改了
index,CAS会直接失败,我们就重新进入循环,读取最新的index值再尝试,直到成功为止。 - 这里把
index改成uint32是因为Go标准库的原子操作对这类基本数值类型支持更直接,能避免不必要的类型转换问题。
额外小提示
这种CAS循环是无锁编程里的常用操作,低竞争场景下比锁的性能更好,因为没有上下文切换的开销;但如果是超高竞争的场景,可能会出现较多的循环重试,这时候锁的表现可能更稳定——不过你的需求用这种方式完全能完美满足。
内容的提问来源于stack exchange,提问作者wllenyj
相关产品推荐
相关产品推荐

