当前位置:首页 > 文章列表 > 文章 > python教程 > Python asyncio.PriorityQueue 怎样保持相同优先级的稳定顺序

Python asyncio.PriorityQueue 怎样保持相同优先级的稳定顺序

来源:17golang原创 2026-10-15 04:44:27 0浏览 收藏

我在把几个异步任务放进优先级队列时,最先遇到的不是“优先级怎么写”,而是同级任务为什么偶尔顺序不固定。asyncio.PriorityQueue 会按最低优先级先取出,官方文档给出的典型队列项是 (priority_number, data);如果 data 是字典或自定义对象,比较走到第二项时还可能直接失败。

官方文档:https://docs.python.org/3/library/asyncio-queue.html

比较稳妥的写法是把队列项扩展成 (priority, sequence, payload):第一项决定优先级,第二项固定同级任务的先后,第三项只保存真正要处理的数据。这样既能保持相同优先级的入队顺序,也不会要求字典或业务对象互相比较。

先看清 PriorityQueue 到底比较什么

asyncio.PriorityQueue 是 asyncio.Queue 的变体,取出顺序是最低值优先。它并不知道“业务上的紧急程度”,只会依赖队列项的可比较关系。因此下面这个结构表达的是“数值越小越优先”:(10, "普通任务") 会排在 (1, "紧急任务") 后面。

真正容易踩坑的是同级项。若两个元素分别是 (1, {"id": 1}) 和 (1, {"id": 2}),比较器已经无法只靠第一项分出先后,继续比较字典就会把排序细节暴露给业务数据。解决思路不是给 payload 强行实现排序,而是为每次入队分配一个独立序号。

Python asyncio.PriorityQueue 中 priority、sequence 和 payload 三个字段的静态比较关系图
图1:PriorityQueue 队列项的静态字段关系说明图;priority 决定优先级,sequence 决定同级顺序,payload 保持业务对象原样。

用 sequence 固定同级任务的先后

把序号放在 payload 前面即可形成稳定的排序键。序号可以用 itertools.count() 产生,它只负责给本次进程里的入队动作编号,不需要把编号写入业务对象。

import asyncio
import itertools

# sequence 只表示入队先后,不改变任务自身的数据结构。
sequence = itertools.count()
queue = asyncio.PriorityQueue()

async def enqueue(priority, payload):
    # 数值越小越先取出;sequence 让相同 priority 仍有确定顺序。
    item = (priority, next(sequence), payload)
    await queue.put(item)

async def main():
    # 三个任务优先级相同,取出顺序由序号保持为 A、B、C。
    await enqueue(5, {"name": "A"})
    await enqueue(5, {"name": "B"})
    await enqueue(5, {"name": "C"})

    for _ in range(3):
        priority, order, payload = await queue.get()
        try:
            # 业务代码只消费 payload,排序字段不泄漏到处理逻辑。
            print(priority, order, payload["name"])
        finally:
            # 每次 get 都要对应一次 task_done,join 才能正确结束。
            queue.task_done()

    # 等待所有已取出的项目都被标记为完成。
    await queue.join()

asyncio.run(main())

这段结构的关键是 tuple 的前两项总能比较:优先级不同先按 priority 排,同级时按 sequence 排。只有前两项都相等才会触及 payload,而单调递增序号使正常入队路径不会走到这一步。

为什么不要让字典或对象承担排序

业务 payload 往往是字典、dataclass 或第三方对象,它们的字段变化不应该悄悄改变调度顺序。把 payload 放到第三项有两个好处:一是排序契约集中在队列封装中,二是消费者不必知道队列内部为何能稳定出队。

队列项比较结果适用性
(priority, payload)同级时会比较 payloadpayload 可比较且不在意同级顺序时才适合
(priority, sequence, payload)同级由 sequence 决定字典、对象和同级 FIFO 场景的通用写法
(-priority, sequence, payload)把较大的业务优先级转成较小的队列值业务约定“数字越大越紧急”时使用

如果业务确实需要同级任务按某个字段再排序,可以明确把那个字段放在 sequence 的位置,并保证它的类型稳定;不要依赖字典插入顺序或对象的默认比较行为。

把生产者和消费者的职责封装起来

实际项目里通常有多个生产者和一个或多个消费者。建议把“生成排序键”放在生产者入口,把 task_done() 放在消费者的 finally 中,避免异常时遗漏未完成任务计数。

import asyncio
import itertools

class StablePriorityQueue:
    def __init__(self, maxsize=0):
        # maxsize 为正数时,put 会在容量用尽后等待空位。
        self._queue = asyncio.PriorityQueue(maxsize=maxsize)
        self._sequence = itertools.count()

    async def put(self, priority, payload):
        # 只在这一层组合排序键,调用方不需要手动维护序号。
        await self._queue.put((priority, next(self._sequence), payload))

    async def get(self):
        # 只返回业务需要的三项,消费者仍可在需要时读取 priority 和 order。
        return await self._queue.get()

    def task_done(self):
        # 委托给内部队列,保持 join 的未完成任务计数一致。
        self._queue.task_done()

    async def join(self):
        # 等待所有 put 进来的项目都完成 task_done。
        await self._queue.join()

async def worker(name, work_queue):
    while True:
        priority, order, payload = await work_queue.get()
        try:
            # 这里替换成真实 I/O 或业务处理;异常仍会触发 finally。
            await asyncio.sleep(0)
            print(name, priority, order, payload)
        finally:
            work_queue.task_done()
稳定优先级队列中生产者、排序键封装、消费者和 join 计数的静态模块关系图
图2:生产者、稳定排序键、消费者和完成计数之间的静态模块关系说明图;它表达职责边界,不是运行截图。

这里没有把消费者写成“处理完就自动结束”。join() 只关心每个入队项目是否最终调用了 task_done(),消费者生命周期仍应由项目自己的取消、关闭或哨兵策略管理。当前文章只讨论排序键和完成计数,不把队列关闭协议混进排序规则。

四个容易被忽略的边界

  1. 优先级方向:官方语义是最低值优先。若业务认为数字越大越紧急,可以存入负值,或者在封装层统一转换,不能让不同生产者各自理解。
  2. 序号的作用域:itertools.count() 只保证当前进程内由同一个计数器产生的顺序。跨进程、跨机器或需要持久化恢复时,应把排序键设计成业务可恢复的字段。
  3. 取消与异常:消费者拿到项目后即使处理失败,也要在 finally 中调用 task_done();否则等待 join() 的协程会一直保留未完成计数。
  4. 容量限制:maxsize 控制的是队列容器容量,不是同一优先级的数量。容量为正时,生产者可能在 put() 处等待,设计时要把这个背压点纳入任务调度。

什么时候该选这个写法

只要任务有“优先级 + 同级按到达顺序处理”的要求,而且 payload 不天然支持比较,就应该使用三元组方案。它的代价很小:增加一个整数序号,并让队列封装多做一次拆包。

如果同级任务本来就允许任意顺序,或者 payload 是简单数字并且需要按 payload 二次排序,二元组也可以。但这个选择应写在队列接口约定里,避免后来有人把字典或对象塞进相同优先级的二元组后才发现比较异常。

常见问题

PriorityQueue 是数字越大越优先吗?

不是,默认是最低值优先。需要“大数更紧急”时,把业务值映射成负数或在封装层转换。

为什么还要调用 task_done?

每次 get() 取出的项目都应对应一次 task_done()。join() 依靠这个未完成任务计数判断是否可以继续。

能否用时间戳代替 sequence?

不建议把普通时间戳当唯一序号:精度、时钟调整和并发下的相等都可能带来歧义。进程内稳定排序用单调计数器更直接;需要跨进程时再设计明确的可持久化排序键。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Python asyncio.Runner 怎样复用同一个事件循环运行多个入口Python asyncio.Runner 怎样复用同一个事件循环运行多个入口
上一篇
Python asyncio.Runner 怎样复用同一个事件循环运行多个入口
Python configparser 怎样转义值里的百分号
下一篇
Python configparser 怎样转义值里的百分号
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    477次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    534次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    554次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    506次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    327次使用