Go 怎么用 heap 实现按优先级领取任务
如果任务不是“先来先服务”,而是要优先领取紧急任务,普通切片配合遍历查找会越来越笨重。Go 标准库的 container/heap 可以把一组任务组织成优先队列:把优先级高的任务定义为“更小”,heap.Pop 就会先返回它。
实现重点只有三件事:Less用大于号实现高优先级优先,修改任务优先级后调用heap.Fix,以及用指针接收者维护队列长度和索引。
heap.Interface的默认语义是最小堆,根元素位于索引 0。- 队列新增和领取都是 O(log n),批量建堆可用
heap.Init。 - 直接改优先级不会自动调整位置,必须调用
heap.Fix。
先把任务模型变成可调整的优先队列
下面的任务包含名称、优先级和当前索引。索引不是业务字段,而是为了让优先级变化时能快速定位元素。Swap 交换任务后必须同步两个索引,否则后续 heap.Fix 可能修错位置。
package main
import (
"container/heap"
"fmt"
)
type Task struct {
Name string
Priority int
index int // 记录任务当前位于堆切片的哪个位置
}
type PriorityQueue []*Task
func (q PriorityQueue) Len() int { return len(q) }
func (q PriorityQueue) Less(i, j int) bool {
// heap 是最小堆;用大于号即可让数值更大的任务先出队。
return q[i].Priority > q[j].Priority
}
func (q PriorityQueue) Swap(i, j int) {
q[i], q[j] = q[j], q[i]
q[i].index = i // 交换后同步新位置
q[j].index = j
}
func (q *PriorityQueue) Push(x any) {
task := x.(*Task)
task.index = len(*q) // 新元素先记录在追加位置
*q = append(*q, task)
}
func (q *PriorityQueue) Pop() any {
old := *q
n := len(old)
task := old[n-1]
old[n-1] = nil // 解除引用,避免队列缩短后继续占住任务
task.index = -1
*q = old[:n-1]
return task
}

这里的 Push 和 Pop 是给 container/heap 内部调用的接口方法;业务代码新增和领取任务时,应调用包级函数 heap.Push、heap.Pop,不要直接调用队列方法。
用 heap.Init、heap.Push 和 heap.Pop 领取任务
已有任务先组成切片,再调用一次 heap.Init 建立堆不变量。以后新增任务使用 heap.Push,领取任务使用 heap.Pop。因为 Less 采用大于号,输出顺序会从高优先级到低优先级。
func main() {
queue := PriorityQueue{
&Task{Name: "生成日报", Priority: 2},
&Task{Name: "修复支付回调", Priority: 9},
&Task{Name: "刷新缓存", Priority: 5},
}
heap.Init(&queue) // 批量建堆,复杂度 O(n)
heap.Push(&queue, &Task{Name: "处理告警", Priority: 8})
for queue.Len() > 0 {
task := heap.Pop(&queue).(*Task)
fmt.Printf("%d - %s\n", task.Priority, task.Name)
}
}
这段程序的领取次序是 9、8、5、2。注意堆只保证根节点是当前最优元素,不保证底层切片整体已经排好序,所以不能把 queue 当成普通排序结果直接遍历。
| 动作 | 调用 | 复杂度 | 用途 |
|---|---|---|---|
| 批量初始化 | heap.Init | O(n) | 让已有切片满足堆约束 |
| 新增任务 | heap.Push | O(log n) | 插入并恢复顺序 |
| 领取任务 | heap.Pop | O(log n) | 取出当前最高优先级任务 |
| 调整任务 | heap.Fix | O(log n) | 修改优先级后重新定位 |
任务优先级变化后为什么要调用 heap.Fix
例如一个普通任务突然升级为紧急任务,直接修改 task.Priority 只改变了字段,不会通知堆重新调整。可以给队列增加一个更新方法,先写入新值,再用任务保存的 index 调用 heap.Fix。
func (q *PriorityQueue) Update(task *Task, priority int) {
if task.index = len(*q) {
return // 任务已出队或索引无效,不再修改堆
}
task.Priority = priority
heap.Fix(q, task.index) // 只调整受影响的路径
}
如果任务已经被 heap.Pop 取走,示例中的 index 会变成 -1,更新方法可以直接返回。若要删除任意位置的任务,则使用 heap.Remove,不要手动从切片中删除后继续使用旧索引。

把示例接入业务时检查这几个边界
第一,优先级相同的任务不自动保证稳定顺序;如果业务要求先进先出,可以再保存递增序号,在 Less 中用序号做第二排序条件。第二,队列通常应由一个拥有者串行修改;若多个 goroutine 同时 Push、Pop 或 Update,需要在外层加锁,container/heap 本身不提供并发保护。第三,空队列取任务前先判断 Len,不要假设 heap.Pop 会返回空值。
常见问题
为什么 Less 写成大于号却叫最小堆?
因为 heap 包按 Less 判断“更小”,用大于号后,业务上的高优先级在比较关系中反而更小,于是会先到根节点。
能不能直接对 PriorityQueue 排序后取任务?
可以排序,但每次新增、领取都要重新排序;优先队列只维护局部堆结构,更适合持续插入和取出。
什么时候用 heap.Init,什么时候用 heap.Push?
已有一批任务时先用 heap.Init,单个任务进入运行中的队列时用 heap.Push。
PHP array_filter 后 JSON 为什么变成对象
- 上一篇
- PHP array_filter 后 JSON 为什么变成对象
- 下一篇
- gfx工具箱打不开怎么排查?关闭游戏、版本选择与发热提醒
-
- Golang · Go教程 | 14分钟前 | go · Slices · BinarySearchFunc ·
- Go 有序结构体切片怎么按字段二分查找
- 366浏览 收藏
-
- Golang · Go教程 | 28分钟前 |
- Go 怎么用双向链表实现固定容量 LRU 缓存
- 321浏览 收藏
-
- Golang · Go教程 | 40分钟前 |
- Go 怎么用环形缓冲保存最近一组采样值
- 110浏览 收藏
-
- Golang · Go教程 | 1小时前 | 数据结构 · Go泛型 · Go 泛型 Comparable 切片去重
- Go 泛型函数怎么给不同类型的切片去重
- 473浏览 收藏
-
- Golang · Go教程 | 1小时前 | 标准库 · 命令行工具 · Go教程 · Go text/tabwriter 命令行表格 列对齐
- Go 命令行表格怎么按列对齐输出
- 233浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go 怎么解析邮件头里的中文主题和发件人
- 485浏览 收藏
-
- Golang · Go教程 | 1小时前 | 网络编程 · Go教程 · DNS解析 · DNS 域名解析 Go net.Resolver
- Go 怎么指定 DNS 服务器解析域名
- 351浏览 收藏
-
- Golang · Go教程 | 2小时前 | 网络编程 · Go教程 · TCP长连接 · Go TCP keepalive net.TCPConn
- Go 长连接怎么设置 TCP KeepAlive 探测参数
- 115浏览 收藏
-
- Golang · Go教程 | 2小时前 | HTTP · go · 认证 · Go net/http BasicAuth HTTP Basic 认证
- Go 内部工具怎么添加 HTTP Basic 认证
- 285浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 159次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 87次使用
-
- ClickPrompt
- ClickPrompt是一款专为AI提示词编写者设计的开源在线工具,支持Stable Diffusion绘图、ChatGPT对话及GitHub Copilot代码辅助。提供Prompt自动生成、一键运行、社区分享及可视化优化功能,帮助用户高效获取精准AI输出。
- 47次使用
-
- PromptHero
- PromptHero是专业的AI提示词搜索引擎与优化平台,支持Stable Diffusion、Midjourney等主流模型。提供海量提示词库、分类搜索、在线课程及社区互动,助力用户高效生成高质量AI图像与文本。
- 30次使用
-
- Stable Diffusion Prompt Book
- 深入解析OpenArt推出的Stable Diffusion Prompt Book,这本免费的开源提示词指南涵盖从基础语法到高级技巧,提供风格化词库与参数建议,助您优化AI绘画生成效果。
- 31次使用
-
- Golang迭代如何在Go中循环数据结构使用详解
- 2022-12-22 148浏览
-
- 详解如何在Go语言中循环数据结构
- 2022-12-22 406浏览
-
- GScript 编写标准库示例详解
- 2022-12-30 369浏览
-
- 关于Golang标准库flag的全面讲解
- 2023-02-25 344浏览
-
- Go语言数据结构之双链表学习教程
- 2022-12-30 280浏览

