前言
在数据结构中,队列遵循着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)消费者执行任务。
(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 :=