golang优先级队列的实现全过程

 

前言

在数据结构中,队列遵循着FIFO(先进先出)的规则。在此基础上,人们引申出了“优先级队列”的概念。

优先级队列,是带有优先级属性的队列,所有的队列元素按照优先级进行排序,消费者会先对优先级高的队列元素进行处理。

优先级队列的使用场景也是非常多的。比如,作业调度系统,当一个作业完成后,需要从剩下的作业中取出优先级最高的作业进行处理。又比如,一个商城的用户分为普通用户和vip用户,vip用户更容易抢到那些秒杀商品。

在本文中,我将和大家一起探讨,golang优先级队列的一种实现方案。

 

你可以收获 golang切片特性golang map特性golang并发场景下的解决方案golang优先级队列的实现思路

 

正文 内容脉络

为了让大家脑海里有个大致的轮廓,我先把正文的大纲展示出来。

基础知识

在正式开始“优先级队列”这个话题之前,我们首先要明确以下的一些golang特性。

切片的特性

元素的有序性非线程安全

map的特性

元素的无序性非线程安全

并发场景下的解决方案

互斥锁:可以对非线程安全的数据结构创建临界区,一般用于同步场景;管道:可以对非线程安全的数据结构进行异步处理 实现思路

既然,我们了解了golang的一些特性,那么,我们接下来就要明确,如何去实现优先级队列了。

我们都知道,无论是哪一种队列,必然是存在生产者和消费者两个部分,对于优先级队列来说,更是如此。因此,咱们的实现思路,也将从这两个部分来谈。

1、生产者

对于生产者来说,他只需要推送一个任务及其优先级过来,咱们就得根据优先级处理他的任务。

由于,我们不大好判断,到底会有多少种不同的优先级传过来,也无法确定,每种优先级下有多少个任务要处理,所以,我们可以考虑使用map来存储优先级队列。其中key为优先级,value为属于该优先级下的任务队列(即管道) 。

2、消费者

对于消费者来说,他需要获取优先级最高的任务进行消费。

但是,如果只按照上面所说的map来存储优先级队列的话,我们是没法找到优先级最高的任务队列的,因为map的元素是无序的。那么,我们怎么处理这个问题呢?

我们都知道,在golang的数据结构里,切片的元素是具有有序性的。那么,我们只需要将所有的优先级按从小到大的方式,存储在一个切片里,就可以了。等到消费的时候,我们可以先从切片中,取出最大的优先级,然后再根据这个key去优先级队列的map中查询,是不是就可以了?

目标规划

想好了实现思路之后,我们就得对接下来的代码实现做一个规划了。

数据结构

存储优先级队列的map存储优先级的切片互斥锁其他......

生产者

添加任务到优先级队列

消费者

从优先级队列获取任务 步步为营 1、数据流

(1)调用NewPriorityQueue() ,初始化优先级队列对象。

(2)初始化优先级队列map。

(3)开启协程,监听一个接收推送任务的全局管道pushChan。

(4)用户调用Push() ,推送的任务进入pushChan。

(5)推送的任务被加到优先级队列中。

(6)消费者从优先级队列中获取优先级最高的一个任务。

(7)消费者执行任务。

2、数据结构

(1)优先级队列对象

type PriorityQueue struct { mLock sync.Mutex // 互斥锁,queues和priorities并发操作时使用 queues map[int]chan *task // 优先级队列map pushChan chan *task // 推送任务管道 priorities []int // 记录优先级的切片(优先级从小到大排列) }

(2)任务对象

type task struct { priority int // 任务的优先级 f func() // 任务的执行函数 } 3、初始化优先级队列对象 func NewPriorityQueue() *PriorityQueue { pq := &PriorityQueue{ queues: make(map[int]chan *task), // 初始化优先级队列map pushChan: make(chan *task, 100), } return pq }

当然,在这个过程中,我们需要对pushChan进行监听。如果有任务推送过来,咱们得处理。

func (pq *PriorityQueue) listenPushChan() { for { select { case taskEle :=