当前位置:首页 > 文章列表 > Golang > Go教程 > Go 怎么用 heap 实现按优先级领取任务

Go 怎么用 heap 实现按优先级领取任务

来源:17golang原创 2026-09-06 06:34:51 0浏览 收藏

如果任务不是“先来先服务”,而是要优先领取紧急任务,普通切片配合遍历查找会越来越笨重。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
}
Go container heap 优先队列中 Task、PriorityQueue、Less、Swap、Push 和 Pop 的静态关系
图1:PriorityQueue 通过 Task、Less、Swap、Push 和 Pop 组成可维护的优先队列结构。

这里的 PushPop 是给 container/heap 内部调用的接口方法;业务代码新增和领取任务时,应调用包级函数 heap.Pushheap.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.InitO(n)让已有切片满足堆约束
新增任务heap.PushO(log n)插入并恢复顺序
领取任务heap.PopO(log n)取出当前最高优先级任务
调整任务heap.FixO(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,不要手动从切片中删除后继续使用旧索引。

Go heap.Fix 调整任务优先级时的 Task index、PriorityQueue 和根节点关系
图2:修改 Priority 后,借助 index 定位任务并由 heap.Fix 恢复优先队列的静态边界。

把示例接入业务时检查这几个边界

第一,优先级相同的任务不自动保证稳定顺序;如果业务要求先进先出,可以再保存递增序号,在 Less 中用序号做第二排序条件。第二,队列通常应由一个拥有者串行修改;若多个 goroutine 同时 Push、Pop 或 Update,需要在外层加锁,container/heap 本身不提供并发保护。第三,空队列取任务前先判断 Len,不要假设 heap.Pop 会返回空值。

常见问题

为什么 Less 写成大于号却叫最小堆?

因为 heap 包按 Less 判断“更小”,用大于号后,业务上的高优先级在比较关系中反而更小,于是会先到根节点。

能不能直接对 PriorityQueue 排序后取任务?

可以排序,但每次新增、领取都要重新排序;优先队列只维护局部堆结构,更适合持续插入和取出。

什么时候用 heap.Init,什么时候用 heap.Push?

已有一批任务时先用 heap.Init,单个任务进入运行中的队列时用 heap.Push

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
PHP array_filter 后 JSON 为什么变成对象PHP array_filter 后 JSON 为什么变成对象
上一篇
PHP array_filter 后 JSON 为什么变成对象
gfx工具箱打不开怎么排查?关闭游戏、版本选择与发热提醒
下一篇
gfx工具箱打不开怎么排查?关闭游戏、版本选择与发热提醒
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    159次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    87次使用
  • ClickPrompt:AI提示词生成与优化工具,支持Stable Diffusion、ChatGPT及代码辅助
    ClickPrompt
    ClickPrompt是一款专为AI提示词编写者设计的开源在线工具,支持Stable Diffusion绘图、ChatGPT对话及GitHub Copilot代码辅助。提供Prompt自动生成、一键运行、社区分享及可视化优化功能,帮助用户高效获取精准AI输出。
    47次使用
  • PromptHero官网:AI提示词搜索、优化与学习平台,支持Midjourney/Stable Diffusion
    PromptHero
    PromptHero是专业的AI提示词搜索引擎与优化平台,支持Stable Diffusion、Midjourney等主流模型。提供海量提示词库、分类搜索、在线课程及社区互动,助力用户高效生成高质量AI图像与文本。
    30次使用
  • OpenArt免费开源指南:Stable Diffusion Prompt Book提示词手册详解
    Stable Diffusion Prompt Book
    深入解析OpenArt推出的Stable Diffusion Prompt Book,这本免费的开源提示词指南涵盖从基础语法到高级技巧,提供风格化词库与参数建议,助您优化AI绘画生成效果。
    31次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码