参考自DPDK官方文档原文:http://doc.dpdk.org/guides-20.02/prog_guide/ring_lib.html

针对自己的理解做了一些辅助解释。

1 前置知识

1.1 CAS

学习无锁队列前先看一个基本概念,CAS原子指令操作。

CAS(Compare and Swap,比较并替换)原子指令,用来保障数据的一致性。

指令有三个参数,当前内存值V、旧的预期值A、更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。

在DPDK中封装后的函数如下:

rte_atomic32_cmpset(&r->prod.head, *old_head, *new_head)

&r->prod.head指向当前内存值,*old_head为执行该操作前将r->prod.head存储到临时变量的值,*new_head为即将更新的值。

只有r->prod.head == *old_head才会将r->prod.head更新为*new_head

1.2 其他ring实现的参考(了解)

1)FreeBSD中Ring实现的参考

在FreeBSD 8.0中添加了以下代码,并在某些网络设备驱动程序中使用(至少在Intel驱动程序中):

2)Linux中无锁Ring的实现

http://lwn.net/Articles/340400/

2 Ring Library

2.1 介绍

ring是一个有限大小的链表,它具有以下属性:

  • FIFO( First Input First Output)简单说就是指先进先出

  • 大小固定,指针存储在表中
  • 无锁实现
  • 多消费者或单消费者出队
  • 多生产者或单生产者入队
  • 批量(Bulk)出队:如果成功,将指定数量的对象出队; 否则失败
  • 批量入库:如果成功,将指定数量的对象入队; 否则失败
  • 爆发(Burst)出队:如果指定数量的对象无法满足,则将最大可用数量的对象出队
  • 爆发入队:如果指定数量的对象无法满足,则将最大可用数量的对象入队

这种数据结构相比于链表队列优势:

  • 更快:比较void *大小的数据,只需要执行单次CAS指令,而不需要执行2次CAS指令
  • 比完全无锁队列简单
  • 适用于批量入队/出队操作。因为指针存储在表中,多个对象出队并不会像链表队列那样产生大量的缓存未命中,此外,多个对象批量出队不会比单个对象出队开销大

缺点如下:

  • 大小固定
  • 它在许多情况下,内存方面的成本比链表列表的成本更高。空环至少包含N个指针。

Ring库的用例包括:

  • DPDK应用之间的信息交互
  • 内存池中的使用

注:

一个Ring被唯一的名字识别,当尝试创建两个名字相同的Ring时,rte_ring_create()函数会在第二次执行时返回NULL。

2.2 Ring实现原理

本节介绍环形缓存的运作方式。ring结构由两对head,tail组成,一对被生产者使用(cons),一对被消费者使用(prod),

在后续介绍中,r->cons.head和r->cons.tail 分别指向消费者的头和尾,r->prod.head和r->prod.tail指向生产者的头和尾。

下文中每种图形代表了环形缓存ring的一个简单状态。局部变量在队列图形的上方表示(如cons_head,prod_head等都是局部变量),ring结构相关变量在队列图形的下方表示(以r->开头)。

2.2.1 单生产者入队

本节介绍当单生产者添加一个对象到ring时发生了什么。在这个例子中,仅只有一个生产者,仅只有生产者的head和tail(r->cons.head、r->cons.tail)索引被修改了,在初始状态, 它们指向相同的位置。

2.2.2.1 入队第一步

使用局部变量保存r->prod.head 和 r->cons.tail,同时prod_next局部变量指向prod_head的下一个元素,若是批量入队就指prod_head的下N个元素。假如ring里没有足够的空间(通过检查cons_tail),入队函数将返回错误。


2.2.2.2  入队第二步

修改ring结构体里的r->prod.head 索引,将它指向局部变量prod_next指向的位置。

将“新增对象的指针”(下图中的obj4)复制到ring里。


2.2.2.3 入队最后一步

一旦添加对象被复制到ring后,ring结构体里的 r->prod.tail索引将指向 r->prod.head的位置,入队操作完成。


2.2.3 单消费者出队

在这个例子中,仅有一个消费者,仅有消费者的head和tail(r->cons.head 和 r->cons.tail)索引被修改了。

初始状态, r->cons.head 和 r->cons.tai指向相同的位置。

2.2.2.1 出队第一步

使用局部变量保存r->cons.head 和 r->prod.tail。 cons_next局部变量指向cons_head的下一个元素,若是批量出队就指向cons_head的下N个元素。假如ring里没有足够的空间(通过检查prod_tail),出队函数将返回错误。


2.2.2.2 出队第二步

修改ring结构体里的r->cons.head 索引,将它指向局部变量cons_next指向的位置。

将对象的指针(上图ring中的obj1)复制到用户传进来的指针中。


2.2.2.3 出队最后一步

ring结构体中的 ring->cons.tail索引指向和 ring->cons.head,局部变量cons_next相同的位置(obj2的位置)。出队操作完成。


2.2.4 多生产者入队

在这个例子中,仅有生产者的head和tail(r->prod.head 和 r->prod.tail)被修改了。初始状态, 它们指向相同的位置。

2.2.4.1 多生产者入队第一步

在两个生产者core中(这个core可以理解成同时运行的线程或进程),各自的局部变量都保存r->prod.head 和 r->cons.tail。 各自的局部变量prod_next索引指向r->prod.head的下一个元素,如果是批量入队,指向下N个元素。

假如ring里没有足够的空间(检查cons_tail获知),入队函数将返回错误。


2.2.4.2 多生产者入队第二步

修改ring结构体里的r->prod.head 索引,将它指向局部变量prod_next指向的位置。这个操作是通过使用 Compare And Swap (CAS)执行完成的, Compare And Swap (CAS)包含以下原子操作:

  • 如果r->prod.head索引和局部变量prod_head索引不相等,CAS操作失败,代码将重新从第一步开始执行。
  • 否则,将r->prod.head索引指向局部变量prod_next的位置,CAS操作成功,继续下一步处理。

注:涉及到了两个core同时对r->prod.head读取,使用了volatile修饰。同样的prod和cons的2对head和tail都是用了volatile修饰。

在下图中,生产者core1执行成功,生产者core2重新从第一步开始执行。


2.2.4.3 多生产者入队第三步

生产者core2中CAS指令重试成功,r->prod.head位置被更新。

生产者core1更新对象obj4到ring中,生产者core2更新对象obj5到ring中。


2.2.4.4 多生产者入队第四步

现在每个生产者core都想通过CAS更新 r->prod.tail索引。生产者core代码中,只有r->prod.tail等于自己局部变量prod_head才能被更新,显然从上图中可知,只有生产者core1才能满足,生产者core1完成了入队操作。


2.2.4.5 多生产者入队最后一步

一旦生产者core1更新了r->prod.tail后,生产者core2也可以更新r->prod.tail了。至此,生产者core2也完成了入队操作。

注:

1)从修改r->prod.head和r->prod.tail的步骤来看,存在“重试”的动作(代码里看是通过while循环不断尝试),因此虽然说是无锁,但是在多生产者情况下还是会有竞争。在创建队列时需要传入是否多生产者的标记,这个标记一定要正确,否则影响性能或准确性。

2)多消费者情况类似,参考上文可以推导出,这里就不再重复。

2.2.5 关于32位取模索引

在前面的图例中,prod_head, prod_tail, cons_head 和 cons_tail 都是用箭头表示的。但在实际的代码实现中,他们的值并不是0和ring大小减一之间的数值。索引的大小范围是0—2^32-1,当访问ring中的数据时,真正的索引等于ring中索引值和掩码与之后的值。32 bit取模的意思是如果索引操作(加减)的结果的值超出了32 bit数据的范围,溢出的值忽略,只看省下的位组成的数。

下面的两个例子帮助解释索引在ring中如何使用的,为了简便,例子操作的是16位而不是32位。另外,关键的四个索引也被定义成16位的整数,现实代码实现是用得32位的整数。

例1:ring包含了11000条目

例2:ring包含了12536个条目

为了便于理解,上面的例子中使用模65536的操作。在真实代码实现中,这是冗长低效的,但是当结果溢出时是自动完成的。

代码实现总是将producer 和 consumer保持0—ring大小减1的距离。 这个特性的好处是我们能在两个32位索引值之间做减法,且差值永远在0一ring大小减1范围内:这也是为什么结果溢出不是什么大问题。

在任何时候,已经使用的条目和空闲的条目永远在0一ring大小减1之间。

uint32_t entries = (prod_tail - cons_head);
uint32_t free_entries = (mask + cons_tail - prod_head);

DPDK 无锁队列Ring Library原理(学习笔记)的更多相关文章

  1. [development][lockless][dpdk] 无锁队列

    dpdk: http://dpdk.org/doc/guides/prog_guide/ring_lib.html#ring-library linux: https://lwn.net/Articl ...

  2. 聊一聊无锁队列rte_ring

    之前用基于dpdk 实现小包快速转发的时候有用到无锁队列!今天就来看看吧!(后续完成了去dpdk化,直接在内核完成快速转发功能) dpdk的无锁队列ring是借鉴了linux内核kfifo无锁队列.r ...

  3. Erlang运行时中的无锁队列及其在异步线程中的应用

    本文首先介绍 Erlang 运行时中需要使用无锁队列的场合,然后介绍无锁队列的基本原理及会遇到的问题,接下来介绍 Erlang 运行时中如何通过“线程进度”机制解决无锁队列的问题,并介绍 Erlang ...

  4. DPDK 无锁环形队列(Ring)详解

    DPDK 无锁环形队列(Ring) 此篇文章主要用来学习和记录DPDK中无锁环形队列相关内容,结合了官方文档说明和源码中的实现,供大家交流和学习. Author : Toney Email : vip ...

  5. 【DPDK】【ring】从DPDK的ring来看无锁队列的实现

    [前言] 队列是众多数据结构中最常见的一种之一.曾经有人和我说过这么一句话,叫做“程序等于数据结构+算法”.因此在设计模块.写代码时,队列常常作为一个很常见的结构出现在模块设计中.DPDK不仅是一个加 ...

  6. HashMap的原理与实 无锁队列的实现Java HashMap的死循环 red black tree

    http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html https://zh.wikipedia.org/wiki/%E7%BA ...

  7. folly无锁队列正确性说明

    folly无锁队列是facebook开源的一个无所队列,使用的是单向链表,通过compare_exchange语句实现的多生产多消费的队列,我曾经花了比较多的时间学习memory_order的说明,对 ...

  8. 高性能无锁队列 Disruptor 初体验

    原文地址: haifeiWu和他朋友们的博客 博客地址:www.hchstudio.cn 欢迎转载,转载请注明作者及出处,谢谢! 最近一直在研究队列的一些问题,今天楼主要分享一个高性能的队列 Disr ...

  9. 无锁队列以及ABA问题

    队列是我们非常常用的数据结构,用来提供数据的写入和读取功能,而且通常在不同线程之间作为数据通信的桥梁.不过在将无锁队列的算法之前,需要先了解一下CAS(compare and swap)的原理.由于多 ...

随机推荐

  1. 2019-2020-1 20199325《Linux内核原理与分析》第四周作业

    start_kernel函数的执行过程 asmlinkage __visible void __init start_kernel(void) { char *command_line; char * ...

  2. Spring Boot中的Properties

    文章目录 简介 使用注解注册一个Properties文件 使用属性文件 Spring Boot中的属性文件 @ConfigurationProperties yaml文件 Properties环境变量 ...

  3. Android 5.0系统默认颜色

    伴随着 Android5.0 的发布也更新了support-v7-appcompat 到 V21,其中增加了 ToolBar.recyclerview.cardview 等控件. Android5.0 ...

  4. 计算2的n次幂htm代码

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. F查询,Q查询,事物,only与defer

    F查询 之前的单表查询多表查询筛选条件都是一个固定的值,那么如何用字段来筛选呢? 比如 :           查询卖出数大于库存数的商品 这个时候我们就可以用到django里面的F查询了 查询示例表 ...

  6. Vue页面权限控制和动态添加路由

    原文转自:点我 页面权限控制 页面权限控制是什么意思呢? 就是一个网站有不同的角色,比如管理员和普通用户,要求不同的角色能访问的页面是不一样的.如果一个页面,有角色越权访问,这时就得做出限制了. Vu ...

  7. 编程语言50年来的变化,我用50种编程语言告诉你“Hello world”怎么写!

    当我们学习一门新的语言时,"Hello, World!"通常是我们所写的第一个程序. 因此,所有程序员在职业生涯中至少完成了"Hello, World!"程序员 ...

  8. 数学--数论--HDU1222 狼和兔子(最大公约数)

    问题描述 有一座周围有n个洞的小山.孔从0到n-1有符号. 兔子必须藏在其中一个洞中.狼以逆时针方向搜索兔子.他第一个进入的洞是一个用0签名的洞.然后,他将每m个洞进入一个洞.例如,m = 2和n = ...

  9. 03 Django下载和使用 三板斧httpresponse render redirect

    简介 是一个为完美主义者设计的web框架 The web framework for perfectionists with deadlines. Django可以使你能够用更少的代码,更加轻松且快速 ...

  10. 前端——Vue CLI 3.x搭建Vue项目

    一.Node安装 windows 1. Node.js (>=8.9, 推荐8.11.0+) Node官网下载 .msi 文件,按步骤下载安装即可. 安装完之后在cmd中输入 node -v,若 ...