container/heap是go标准库中唯一无需第三方依赖的优先级队列实现路径;需自定义类型并完整实现heap.interface五方法,push/pop用指针接收者,首次使用前调heap.init,less逻辑须与业务语义对齐且支持二级比较,所有操作须加锁,动态改优先级需维护index字段并调heap.fix。

container/heap 是唯一靠谱的起点
Go 标准库没有现成的优先级队列类型,container/heap 是你唯一无需引入第三方、可控性强、性能明确的路径。别试图用 []*Task + sort.Slice 每次插入后重排——它时间复杂度是 O(n log n),而且 heap.Pop 会直接 panic 或返回错误任务,因为底层切片根本不满足堆序。
-
container/heap不是“队列”,只是一个堆操作工具包;你必须自己定义类型并实现全部五个heap.Interface方法 -
Push和Pop必须用指针接收者,否则修改不到底层数组 - 首次使用前必须调
heap.Init(&pq),漏掉会导致heap.Pop返回随机元素 - 不要用匿名结构体初始化队列(如
pq := PriorityQueue{}),它不可寻址;应写var pq PriorityQueue或pq := new(PriorityQueue)
Less 函数写错,整个调度就反了
Less(i, j int) bool 返回 true 表示索引 i 的元素该排在 j 前面(即更早被 Pop)。这个逻辑方向必须和你的业务语义对齐,否则高优任务永远出不来。
- 若约定“数值越小越紧急”(比如
Priority = 0是最高优),则写pq[i].Priority - 若约定“数值越大越紧急”(比如
Priority = 100是最高优),则写pq[i].Priority > pq[j].Priority - 必须加二级比较:相同
Priority时再比CreatedAt或ID,否则同优任务顺序不可预测,容易饥饿 - 禁止在
Less里做任何可能阻塞或 panic 的操作(如 DB 查询、HTTP 调用)
并发读写不加锁,100% 出问题
container/heap 完全不带同步语义。多个 goroutine 直接调 heap.Push 或 heap.Pop,轻则数据竞争,重则触发 container/heap: heap invariant violated panic,或者返回已损坏的任务。
- 所有公开方法(
Push、Pop、Peek、Len)都必须包裹在sync.Mutex或sync.RWMutex里 - 别只给
Push加锁而忽略Peek——如果Peek返回指针且外部修改了字段,堆结构就乱了 - 用
sync.RWMutex时,Push/Pop是写操作,Peek/Len可读,但一旦返回指针并被写,就得升级为写锁 - 调度主循环里不能只锁
Pop那一行——从Lock到Unlock必须覆盖整个原子操作(取任务 + 发送到 worker channel)
动态改优先级不是 task.Priority = x 就完事
直接改任务字段不触发堆重排。下次 Pop 还是按旧优先级出队,根本不会感知到变化。高频更新时全量 heap.Init 开销太大,O(n) 会卡顿。
- 每个任务结构体必须带
index int字段,记录它当前在堆切片中的下标 -
Swap方法里必须同步更新两个元素的index - 改完
task.Priority后,立刻调heap.Fix(&pq, task.index)—— 时间复杂度O(log n),安全高效 - 如果任务可能被取消,结构体加
cancelled bool字段,Pop后检查,跳过已取消任务(lazy cancel)
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











