业务开发算法 50 讲
黄清昊
Hashdata 数据库内核工程师,LeetCode 高赞答主,公众号微扰理论作者
23292 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
业务开发算法 50 讲
15
15
1.0x
00:00/00:00
登录|注册

03|双端队列:并行计算中的工作窃取算法如何实现?

根据当前迭代器位置进行缓冲区的释放和调整
根据内存空间情况进行扩容
使用双端队列避免进程间的竞争
允许先完成任务的进程从其他进程的队列盗取任务
多开多个队列存储任务,每个进程消费一个队列
公平地分配任务到每一个进程,避免进程闲置
pop操作
push操作
实现了operator++和operator--,记录迭代器在当前缓冲区的位置
维护一个map成员变量管理缓冲区
首尾插入和删除的时间复杂度为O(1)
由一段段连续的空间组成,通过类似数组的结构拼接地址信息
通过数组或链表模拟实现双端循环队列
区别于数组的高复杂度头部插入操作
两端开口的队列,支持在头尾两端进行进队和出队操作
插入操作在队列尾部进行,删除操作在队列头部进行
先进先出的序列式数据结构
实现队列的链表实现
实现
目的
基础操作
迭代器
内存布局
双端队列
队列
课后作业
工作窃取算法
Deque实现
双端队列
双端队列和工作窃取算法

该思维导图由 AI 生成,仅供参考

你好,我是微扰君。
目前我们已经学习了 vector 动态数组和 list 双向链表两种 STL 中的序列式容器了,今天我们继续学习另一种常见的序列式数据结构,双端队列。
在并行计算中,我们常常会用多进程处理一些复杂的计算任务。为了能够通过多进程加速计算,我们除了需要对任务进行合理的切分,也需要将任务合理公平地分配到每一个进程。简单来说就是,我们希望每个进程都不至于闲着。那怎么样能做到这件事呢?
其实有一种非常常用的算法,工作窃取算法,就可以用来达成这个目标,它就需要用到我们今天的主角——双端队列。

队列

要介绍双端队列,我们先来聊一聊队列,queue。什么是队列呢?
从概念上来说其实非常好理解,因为它的特性和“队列”这个词在现实生活中的意思是一致的,那就是 FIFO 先进先出。简单来说就是排队。
比如说现在到很多餐厅就餐,服务员都会给你发一个号码让你排队,等有空位的时候,服务员叫号是按照取号的顺序来的,肯定是先来取号的人结束排队去入座;这样的约束就是先进先出。
显然这种先进先出的队列也是一种典型的序列式数据结构;和数组最大的区别就在于,它是一个有约束的序列式数据结构,因为先进先出的特性要求我们,所有的插入操作必须在队列的尾部进行,而所有的删除操作则必须在队列的头部进行。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了双端队列在并行计算中的工作窃取算法的实现。首先,文章详细讲解了STL中双端队列的实现,包括内存布局和迭代器的实现。双端队列的内存布局具有list和vector的特点,通过一段段连续的空间和map的管理实现了高效的插入和删除操作。接着,文章探讨了双端队列的基本操作,包括push和pop的实现原理。文章还分析了C++选择基于map分段存储的双端队列的原因,以及与vector和list的比较。最后,文章引出了工作窃取算法需要用到双端队列的思路,并留下了课后作业,鼓励读者深入探讨队列的实现方式。整体而言,本文对于想要深入了解并行计算中的工作窃取算法的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《业务开发算法 50 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(9)

  • 最新
  • 精选
  • SevenHe
    map如果只是用于维护block的索引,本身更新的频率比较低,似乎list比vector更适合,为何不直接使用链表呢?这样不需要考虑扩容和缩容的问题了

    作者回复: 这个问题非常好!看得出来你是有思考的同学~ 我个人的理解,也不一定正确,可能主要是因为 stl::deque 支持了 at 方法;用数组就可以比较快速的取到特定的元素; 这点我在专栏里可能没有说明,之后会调整一下。 至于为什么deque要支持at方法,我还不是特别清楚。

    2021-12-22
    11
  • Paul Shan
    Deque的实现分了两层,第一层是不定长的循环对列管理数据块指针,第二层是定长的数据块,管理实际元素的存取。 和链表相比,这种实现因为有了定长的数据块,可以减少添加和删除内存的数目,也省去了每个节点的指针。有了第一层不定长的循环队列,对于插入和删除元素都能做到O(1)的均摊复杂度,这里使用的指针数目是使用双向链表1/2m,(m是块的大小)

    作者回复: 嗯嗯 说的很对

    2021-12-16
    8
  • 双端队列的两种实现:链表和数组 https://github.com/hzq-qqq/-1。能力有限,有错误的地方,希望指正

    作者回复: 很认真呀;感觉问题不是很大;晚些我仔细看一下。 可以加我vx:constant_variation 一起讨论

    2021-12-25
  • 灵茶山艾府🎈
    有一种双端队列的实现方法是用两个 queue,头对头,这样也可以做到类似 deque 的效果。但是为什么 STL 不采用这种实现方式呢,疑惑。

    作者回复: 哈哈哈 能稍微展开讲解一下吗 我还不是很确定具体的做法

    2021-12-16
    3
  • 那时刻
    请问老师,如果 map 使用率已经超过一半,我们就可以重新申请更大的空间,把老的 map 上的数据拷贝到新的区域。 请问什么这么做呢? 如果不重新申请大的内存,而是增加一个block,如此岂不是节省了拷贝的开销?

    作者回复: 增加一个block没法保证数据是连续存储的哈 和vector扩容的时候不原地扩容是一个道理

    2021-12-16
  • 还有一点 这个玩意我感觉和go的gmp 一样一样的

    作者回复: 嗯嗯 GMP好像确实用了工作窃取算法 具体的细节我也不是很确定了;欢迎补充 可以+V: constant_variation 和我交流

    2021-12-16
    2
  • 简单的总结: 看了下老师的文字描述,感觉这个deque在我的理解上就是 map是一个不定长数组了 然后里面的每个元素就是一段连续的空间(也是节点数组) 这样就可以拼接起来实现双端队列。 然后对于扩容操作 如果发现某一个端点在map层用完了 那么判断是否超过总容量的百分之五十 如果没有超过证明某一段比较数据集中,另外一点数据较少可以移动到中间来 无需扩容,反之扩容 在map层 其他的主要就是维护其他指针了 保证push pop对应的位置 主要是看不明白cpp代码 准备去用go实现一下 以上是小总结如果有错误希望大家指正

    作者回复: 说的非常正确;欢迎用golang实现;可以另外提一份PR到我的仓库 https://github.com/wfnuser/Algorithms 哦

    2021-12-16
    2
  • 徐晓桐
    你好 老师 我不太明白 STL 为啥不直接用双链表实现双端队列 ?
    2022-10-21归属地:上海
  • Geek_ef214b
    老师你好,我想请问多线程环境下双端队列和FIFO队列在工作窃取算法中有什么区别?线程对队列进行Push或Pop不都要上锁吗?对效率的提升有什么帮助?
    2022-03-26
收起评论
显示
设置
留言
9
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部