ring.next() 和 ring.prev() 是遍历 ring 的唯一安全方式;必须从起点出发,循环调用 next() 并判断 r == start 以终止,否则易致无限循环或 panic。

ring.Next() 和 ring.Prev() 是遍历的唯一安全方式
Go 标准库 container/ring 的 Ring 不是 slice,没有索引访问能力,也不能用 for range。想“遍历”,必须靠指针移动:调用 Next() 或 Prev() 逐步推进。直接对 Ring.Next 字段赋值或手动修改 next/prev 指针会破坏环结构,尤其在并发或重置后极易出错。
常见错误现象:panic: runtime error: invalid memory address,往往是因为遍历时没判断是否回到起点,导致无限循环或越界解引用。
- 从某个
*ring.Ring开始,调用r = r.Next(),直到r == start才停止 - 不要用
for i := 0; i —— <code>Len()是 O(n) 的,且遍历时长度可能动态变化 - 如果只读不修改,遍历过程无需加锁;但若其他 goroutine 可能调用
Link()或Unlink(),需外部同步
重置 ring 时必须重新初始化,不能复用旧指针
所谓“重置”,比如清空所有元素并准备重新填充,ring 没有 Clear() 方法。常见误操作是把 r.Value = nil 然后反复 r = r.Next(),但这只是清数据,环结构(指针链)仍存在,且 Len() 不变,后续 Do() 或遍历仍会访问到旧节点。
正确做法是新建一个 ring:用 ring.New(n) 创建指定长度的新环,或用 ring.New(0).Link(ring.New(1)) 构造单节点环再扩展。旧 ring 若无其他引用,会被 GC 回收。
- 重置后不要继续用原来的
*ring.Ring变量作为起始点,它可能指向已失效节点 -
ring.New(0)返回的是一个长度为 0 的环(只有一个哨兵节点),此时Len()返回 0,Next()仍返回自身,可安全遍历(0 次) - 如果原 ring 被多个地方引用,重置前需确保所有使用者已切换到新 ring,否则会出现“幽灵遍历”
ring.Do() 适合简单遍历,但无法中途退出或获取索引
ring.Do(f func(interface{})) 是标准库提供的遍历方法,内部按 Next() 顺序调用函数。它简洁安全,但有两个硬限制:无法在满足条件时提前跳出,也无法知道当前是第几个元素。
典型使用场景:批量打印、统一初始化、触发副作用(如通知)。一旦需要“找到第一个匹配项就返回”,就必须手写循环 + 判断。
-
Do()在 ring 长度为 0 时什么也不做,不会 panic - 函数
f中修改Value是安全的,但禁止修改ring结构(如调用Link()) - 如果
f可能 panic,Do()不会 recover,整个遍历会中断
查找元素必须手写循环,没有内置 Find 方法
container/ring 没有 Find()、Contains() 或类似方法。查找只能靠自己走一遍环,逐个比对 Value。注意:比较逻辑取决于你的 Value 类型——如果是指针,要小心是否比地址还是比内容。
示例:查找值等于 v 的节点,并返回其 *ring.Ring 指针:
func find(r *ring.Ring, v interface{}) *ring.Ring {
if r == nil {
return nil
}
start := r
for {
if r.Value == v { // 注意:这里用 ==,仅适用于可比较类型
return r
}
r = r.Next()
if r == start {
break
}
}
return nil
}
- 若
Value是结构体或切片,需用reflect.DeepEqual或自定义比较函数 - 查找失败时返回
nil,不是空 ring;别拿这个nil去调用Next() - 查找过程中 ring 被并发修改,结果不可靠——除非你已加锁或确保只读
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











