##特征
- 让人震惊的[快速性能] [基准]。
- 单头实现。把它放在你的项目中。
- 完全线程安全的无锁队列。同时使用任意数量的线程。
- C ++ 11实现 - 在可能的情况下(c11中的move语义)移动元素(而不是复制)。
- 模板化,不需要专门处理指针 - 内存管理为您。
- 元素类型或最大数量不受人为限制。
- 内存可以预先分配一次,也可以根据需要动态分配。
- 完全可移植(无需汇编;全部通过标准C ++ 11原语完成)。
- 支持超快速批量操作。
- 包含低开销阻塞版本(BlockingConcurrentQueue)。
- 异常机制。

##使用的理由
C ++没有那么多的完全无锁的队列。 Boost有一个,但是它仅限于 有琐碎的各种构造、析构函数的对象
英特尔的TBB队列不是无锁的,并且也需要简单的、琐碎的构造函数。
有许多学术论文用C ++实现无锁队列,但可用的源代码是
很难找到,测试更是如此。
这个队列不仅比其他人(大部分)有更少的限制,而且[是业界标准]。
它已被相当充分的测试,并提供像**批量入队/出队**的高级操作
(我的新设计达到甚至超越了,一个非并发队列的速度)。
总之,C++开源世界里有一个无锁队列形状的洞,我就开始
用最快,最完整,经过充分测试的设计和实施来填补它。
结果是`moodycamel :: ConcurrentQueue` O(∩_∩)O~

##不使用的理由
快速同步机制是从未发生的那种。从根本上说,
并发数据结构需要同步机制,同步是需要时间的。这需要一定的开销
当然,为了尽量减少开销,你可以避免线程之间共享数据
但是为什么还要使用并发数据结构呢?而且他们也太麻烦了! (而且的确,
有时同时共享数据是不可避免的。)

我的队列是**不可线性化的
其设计假定生产者是独立的;如果情况并非如此,那么你的生产者
是以某种方式相互协调,然后生产者的元素按相对于协调所形成的顺序排队,
如果不按相对顺序排队的这种情况影响
你的用例,你可能得用另一个更好的实现;无论如何要知道,这是一个重要的限制

我的队列也**不关心NUMA**,并在内部做了很多内存重,这意味着它可能不会
在NUMA体系结构上特别好;然而,我不知道任何其他的无锁队列*是否考虑了NUMA

最后,队列是**非连续一致的**;

##高级设计
元素使用连续的块而不是链表进行内部存储,以获得更好的性能。
队列由一组子队列组成,每个生产者对应一个子队列。当一个消费者
想要出队一个元素,它检查所有的子队列,直到找到一个不是空的。
所有这些对于队列的用户来说都是透明的,但是它大部分只是在TM上运行。

然而,这种设计的一个特别的后果(这似乎是非直观的)是,如果两个生产者
在排队的同时,元素之间没有明确的排序。
通常这很好,因为即使有完全可线性化的队列,生产者线程间也会有竞赛,
所以你不能依靠有序化。但是,如果由于某种原因你在两个生产者线程间做了额外的显式同步
这样就定义了排队操作之间的总顺序,你可能会期待着
元素将以相同的顺序出现,哈哈,我的队列不提供这种保证。
从语义上讲,并不是真正的两个独立的生产者,而是一个恰好被传播的东西
跨多个线程。在这种情况下,您仍然可以通过创建与我的队列建立完整的排序
一个单一的生产者令牌,并使用这两个线程排队(注意同步访问令牌,
当然,但是已经有了额外的同步)。

我已经写了一个更详细的[内部设计概述] [博客],以及[全部
细节设计] [设计],在我的博客。最后,

[来源] [来源]本身是可供查阅有关执行的人。

=====================================================================================================

我被无锁的bug咬了,在完成单生产者单消费者无锁队列之后,我决定设计和实现一个更一般的多生产者,多消费者队列,
并且看看它如何与现有的无锁C++队列堆积起来。经过一年多的空闲时间的开发和测试,现在是公开发布的时候了。
您可以从GitHub获取
队列工作的方式很有趣,我想,这就是这篇博文的意思。顺便说一下,在一篇姊妹博客文章中可以找到更详细,更全面(也更干燥)的描述。
共享数据:哦,这个灾难
乍一看,一个通用的无锁队列看起来相当容易实现。事实并非如此。问题的根源在于相同的变量必然需要在多个线程间共享。
例如,采取一种通用的基于链表的方法:至少需要共享列表的头部和尾部,因为消费者都需要能够读取和更新头部,并且生产者
也需要能够更新尾巴。
这听起来不错,但是当一个线程需要更新多个变量来保持队列处于一致状态时才会出现真正的问题 - 只有单个变量才能保证原子性,
复合变量的原子性(结构体)几乎肯定会导致使用锁。
例如,如果消费者从队列中读取最后一个元素并只更新了头部呢?然后 
由于最后一个元素很快就会被释放,尾巴不应该指向它,但是,消费者可能会被操作系统中断,并在更新尾部之前暂停几毫秒,在此期间,尾部可能会被另一个线程更新,
然后第一个线程将其设置为空为时已晚。
无锁编程的关键是共享数据。通常最好的办法是设计一个算法,不需要更新多个变量来保持一致性,
或者增量更新仍然使数据结构保持一致的状态。可以使用各种技巧,比如从不释放分配的内存(这有助于从不是最新的线程读取),
在指针的最后两位存储额外的状态(这与4字节对齐的指针一起工作) ,并引用计数指针。
但是像这样的技巧只能走得这么远。真正的努力将发展到算法本身。

我的队列
线程间分享的数据越少越好。所以,不是使用一个单一数据结构线性化所有操作,而是使用一组子队列 - 每个生产者线程一个。
这意味着不同的线程可以完全平行的插入元素,彼此独立。
当然,这也使得出队稍微复杂一些:现在,我们必须在出队时检查每个子队列中的元素。有趣的是,事实证明,
从子队列中抽取元素的顺序并不重要。来自给定生产者线程的所有元素在离队时仍将以相同顺序离队(因为子队列保留该顺序),
尽管来自其他子队列的元素可能交织。交错元素是可以的,因为即使在传统的单一队列模型中,
元素从不同生产者线程获得的顺序也是非确定性的(因为不同生产者之间存在竞争条件)。 
这个方法唯一的缺点就是如果队列是空的,每一个子队列都必须被检查以确定这个情况(在一个子队列被检查的时候,一个空的队列可能会变得非空 - 
但实际上这不会造成问题)。However,在非空案例中,争议总体上少得多,因为子队列可以与消费者“配对”。
这将数据共享降低到接近最佳水平(每个消费者只与一个生产者匹配),而不会失去处理一般情况的能力。
这种配对是通过考虑到生产者成功拉出的最后一个子队列(基本上是消费者的亲和力)的启发式来完成的。
当然,为了进行这种配对,需要在出线呼叫之间维护一些状态 - 这是通过用户负责分配的“消费者特定令牌”完成的。
请注意,令牌是完全可选的 - 队列仅仅恢复为搜索每个子队列中没有一个的元素,这是正确的,当涉及多个线程时,会稍微慢一些。

所以,这是高级设计。那么每个子队列中使用的核心算法呢?那么,我不是基于节点的链表(这意味着不断地分配和释放或重用元素,
而且通常依靠比较和交换循环,在重度竞争下可能很慢),我的队列基于数组模型。我没有链接各个元素,而是有几个元素的“块”。
队列的逻辑头部和尾部索引用原子增量整数表示。在逻辑索引和块之间存在一种用于将每个索引映射到该块和该块的子索引的方案。
排队操作只是增加尾部(记住每个子队列只有一个生产者线程)。

如果发现头部小于尾部,那么出队操作会增加头部,
然后检查头部是否意外地增加了头部超过尾部(这可能在争用情况下发生 - 每个子队列有多个消费者线程) 。如果它过度增加头部,
则增加校正计数器(使得队列最终一致),如果不是,则继续并增加另一个整数,这给出了实际的最终逻辑索引。最终索引的增量
总是会在实际队列中产生有效的索引,而不管其他线程正在做什么或已经完成了什么;这是有效的,因为最终的索引只有在保证至少
有一个要出队的元素时才会增加(当第一个索引被递增时,检查这个元素)。

所以你有它。入队操作是用一个原子增量完成的,用两个原子增量完成一个出队,否则就是一个额外的。
(当然,这是折扣所有块分配/重用/参考计数/块映射goop,虽然很重要,不是很有趣 - 在任何情况下,这些成本的大部分是分摊在整个块的价值元素)。
这个设计真正有趣的部分是它允许极其高效的批量操作 - 就原子指令而言(往往是一个瓶颈),
在一个块中排列X个项目的成本与排队单个项目(同上出列),只要它们在同一个块中。这是真正的性能收益来的地方:-)

================================================================================================

这篇文章详细地介绍了我设计的一个高效的无锁队列,它支持多个并发的生产者和消费者(一个MPMC队列)。
我的C ++ 11实现这个设计可以在GitHub上找到。包括基准测试结果在内的更高层次的队列概述可以在我介绍我的实现的最初的博客文章中找到。
一个关键点是,元素从队列中出来的总的顺序是不相关的,只要它们在给定的线程上的顺序与它们在另一个线程上出现的顺序相匹配即可。
这意味着队列可以安全地实现为一组独立队列,每个队列一个队列;
编写一个单生产者,多消费者的无锁队列比编写多生产者,多消费者队列要容易得多,而且可以更有效地实现。 
SPMC队列可以通过让消费者根据需要从不同的SPMC队列中拉出(这也可以通过一些聪明的办法来高效地完成)来推广到MPMC。
在典型情况下,启发式算法用于加速出队,尽可能地将消费者与生产者配对(这极大地降低了系统中的整体争用)。

除了高层次的SPMC队列设计之外,队列的另一个关键部分是核心排队算法本身,这是我自己设计的全新的核心排队算法,而不像我听说过的那样。
它使用一个原子计数器来跟踪有多少元素可用,并且一旦有一个或多个元素被声明(通过递增相应的元素消耗计数器并检查该增量是否有效),
原子索引可以安全地递增获取要引用的元素的实际ID。然后这个问题被简化为将整数ID映射到单个元素,而不必担心引用相同对象的其他线程(每个ID只被赋予一个线程)。
详情如下!

系统总览
队列由一系列单生产者,多用户(SPMC)队列组成。每个生产者有一个SPMC队列;消费者使用启发法来确定从下一个消耗哪些队列。
队列是无锁的(尽管不是等待)。它的设计很健壮,并且允许以很小的附加开销(相对于单个项目)批量排队和出队。
一些线程本地数据是每个生产者所必需的,并且线程本地数据也可以可选地用于加速消费者。此线程本地数据可以与用户分配的令牌相关联,或者为了简化接口,
如果用户没有为生产者提供令牌,则使用无锁哈希表(键入当前线程ID)查找线程本地生成器队列:为每个显式分配的生产者标记创建一个SPMC队列,
为每个生成项目而不提供标记的线程创建一个SPMC队列。由于令牌包含了多少特定于线程的数据,因此它们不应该从多个线程同时使用(尽管可以将令
牌的所有权转移给另一个线程;特别是,这允许令牌在线程池任务内部使用,即使运行任务的线程改变了部分路径)。

所有的生产者队列将自己链接到一个无锁的链表中。当一个显式的生产者不再有元素被添加到它(即它的标记被销毁)时,它被标记为与任何生产者无关联,但是它被保存在列表中,并且其内存不被释放;下一个新的生产者重新使用旧生产者的内存(无锁生产者列表是这种方式的附加)。由于无法知道给定线程是否使用数据结构完成,所以隐式生成器从不被销毁(直到高层队列本身)。请注意,最差情况下的出队速度取决于有多少个生产者排队,即使它们全是空的。

显式生成器队列与隐式生成器队列的生命周期有一个根本的区别:显式生命周期与生命周期的生命周期有关,而隐式生命周期具有无限生命周期,并存在于高层队列本身。正因为如此,为了最大限度地提高速度和内存使用率,使用了两个略有不同的SPMC算法。一般来说,显式生成器队列的设计稍微快一点,而内存的生产稍微多些,而隐式生成器队列的设计稍微慢一些,但会将更多内存回收到高级队列的全局池中。为了获得最佳速度,请始终使用明确的标记(除非您觉得太不方便)。

任何分配的内存只在高级队列被销毁时释放(虽然有几个重用机制)。内存分配可以在前面完成,如果没有足够的内存(而不是分配更多内存),操作将失败。各种默认大小参数(以及队列使用的内存分配函数)

======================================================================================================

生产者队列(SPMC)设计
隐式和显式版本设计
生成器队列由块组成。最初,它开始没有块。每个块可以容纳固定数量的元素(所有块具有相同的容量,是2的幂)。
此外,块包含一个标志,指示填充的槽是否已被完全消耗(由显式版本用于确定块何时为空)以及完全
出队的元素数的原子计数器(由隐式版本来确定块何时为空)。

为了无锁操作的目的,生产者队列可以被认为是一个抽象的无限数组。尾部索引标示生产者填充的下
一个可用插槽;它也是已经入队的元素数量(入队计数)的两倍。尾部索引完全由生产者编写,并且
总是增加(除非溢出并且环绕,对我们来说仍然被认为是“增加”)。生产一个元素微不足道,
因为只有一个线程正在更新涉及的变量。头部索引标示接下来可以消耗的元素。头指数由消费者自动递增,
可能同时发生。为了防止头部索引达到/超过可见的尾部索引,使用另外的原子计数器:出队计数。
出列计数是乐观的,也就是说,当他们推测性地认为有出队的东西时,它就会增加。如果增加后的出队
计数值小于入队计数(tail),则保证至少有一个出队元素(甚至考虑到并发),并且可以安全地增加
头指数,知道它会比之后的尾巴指数少。另一方面,如果在递增之后出队计数超过(或等于)尾部,
那么出列操作失败并且出列计数逻辑递减(以使其最终与入队计数一致):这可以通过直接递减出队计数,
而是(增加并行性并且保持所涉及的所有变量单调递增),反而递增出队过量使用计数器。为了得到出列
计数的逻辑值,我们从出列计数变量中减去出列超出许可值。

当消耗时,一旦如上所述确定了有效索引,则仍然需要将其映射到该块中的块和偏移;某种索引数据结构
被用于这个目的(哪一个取决于它是一个隐式队列还是显式队列)。最后,元素可以被移出,并且某种
状态被更新,以便最终可能知道块何时完全耗尽。关于这些机制的完整描述在下面的各个部分中提供,
其中涵盖了隐含的和显式的细节。

如前所述,尾部和头部索引/计数最终会溢出。这是预期的并且被考虑在内。索引/计数因此被认为是存在
于最大整数值的大小的圆上(类似于360度的圆,其中359在1之前)。为了检查一个索引/计数,比如a是
否在另一个之前,比如b,(即逻辑小于),我们必须确定a是否通过圆上的顺时针圆弧更接近于b。使用
以下循环少于的算法(32位版本):a <b变为a - b>(1U << 31U)。 a <= b变成a-b-1ULL>
(1ULL << 31ULL)。请注意,循环减法“正常工作”与正常无符号整数(假设二进制补码)。
注意确保尾部索引不会超过头部索引(这会破坏队列)。请注意,尽管如此,在技术上仍然存在一种
竞争条件,即消费者(或生产者)在这种情况下所看到的指数值是如此的陈旧以致于它几乎是一个整数值
(或更多)队列的内部状态变为损坏。然而,在实践中,这不是问题,因为需要花费一些时间才能
通过2 ^ 31的值(对于32位索引类型),而其他核心则会看到更新的东西。事实上,许多无锁算法都是
基于相关的标记指针习惯用法,其中前16位用于重复递增的标记,第二个16位用于指针值;这依赖于类似
的假设,即一个核心不能使标签增加超过2 ^ 15倍而其他核心不知道。尽管如此,队列的默认索引类型
是64位宽(即使是16位似乎也足够,即使在理论上也可以阻止任何潜在的竞争)。

显式生产者队列
显式生成器队列被实现为一个循环的单链表块。在快速路径上是免等待的,但是当需要从块池(
或分配的新块)获取块时,仅仅是无锁的;这只发生在块的内部缓存全部满的时候(或者没有,在开始时
就是这样)。

一旦块被添加到显式生成器队列的循环链表中,它就不会被删除。尾部块指针由生产者维护,指向哪些
元素当前被插入的块;当尾块已满时,检查下一个块以确定是否为空。如果是,则尾块指针更新为指向
该块;如果不是,则在当前的尾部块之后立即申请并插入新的块,然后将其更新为指向这个新的块。

当一个元素完成从一个块出队时,每个元素标志被设置为表明该槽已满。 (实际上,所有的标志都是
从set开始的,只有当slot变空时才关闭。)生产者通过检查所有这些标志来检查块是否为空。如果
块大小很小,那就足够快了;否则,对于较大的块,而不是标志系统,块级原子计数在每次消费者完
成一个元素时递增。当这个计数等于块的大小,或者所有的标记都关闭时,块是空的,可以安全地重新
使用。

为了在常量时间内对块进行索引(即从出队算法中快速找到元素所在的块),使用循环缓冲区(连续数组)。这个指数由生产者维护;消费者从中读取,但从不写入。数组的前面是最近写入的数据块(尾部数据块)。数组的后面是可能包含元素的最后一个块。把这个指标(从高层次的角度来看)作为历史的一个单一长长的带子是有帮助的。只要生产者在另一个块上启动(可以是新分配的或者从其循环列表中重新使用的),前端就增加。只要已经在循环列表中的块被重新使用(因为块只在空时被重新使用,在这种情况下增加后部总是安全的)。而不是明确地存储尾部,保留所使用的槽的数量的计数(这避免了在循环缓冲器中需要备用元素,并且简化了实现)。如果索引中没有足够的空间来添加新项目,则会分配一个新的索引数组,这是前一个数组大小的两倍(显然,只有在允许分配内存的情况下才允许这样做 - 如果不允许,整个队列操作失败)。由于消费者仍然可以使用旧索引,因此它不会被释放,而是被简单地链接到新索引(这形成了链接的索引块,当高层队列被破坏时可以被正确地释放)。当生产者队列的排队计数增加时,它释放对索引的所有写操作;当消费者执行获取(其已经需要用于出列算法)时,则消费者看到的任何索引都将包含对消费者感兴趣的块的引用。由于块的大小是相同的,并且2的幂,我们可以使用移位和掩码来确定我们的目标块与索引中的任何其它块(以及目标块中的偏移)相偏移的块的数量,只要我们知道在给定块中的基本索引指数。因此,索引不仅包含块指针,还包含每个块的相应基本索引。在索引中被选作参考点(用于计算偏移)的块必须不被生产者在被使用时覆盖 - 使用索引的(感知的)前端作为参考点保证了这一点(知道块索引至少和在我们查找的出列索引之前的入队计数一样最新),索引的前面必须在目标块的前面或前面,并且目标块永远不会在索引中被覆盖,直到它(以及之前的所有块)都是空的,并且直到出列操作本身完成之后它才能是空的。指数大小是2的幂,这允许前/后变量的更快缠绕。

显式生成器队列需要用户分配的“生产者令牌”在排队时传递。这个令牌只包含一个指向生产者队列对象的指针。当令牌被创建时,相应的生产者队列被创建;当令牌被销毁时,生产者队列可能仍然包含未消耗的元素,因此队列本身超出了令牌。事实上,一旦分配,生产者队列永远不会被销毁(直到高层队列被破坏),

C++ concurrentqueue资料相关推荐

  1. 栈(Stack)与队列(Queue)

    定义 栈:后进先出(LIFO-last in first out):最后插入的元素最先出来. 队列:先进先出(FIFO-first in first out):最先插入的元素最先出来. 图示 在这里插 ...

  2. [一起读源码]走进C#并发队列ConcurrentQueue的内部世界 — .NET Core篇

    在上一篇<走进C#并发队列ConcurrentQueue的内部世界>中解析了Framework下的ConcurrentQueue实现原理,经过抛砖引玉,得到了一众大佬的指点,找到了.NET ...

  3. 项目优化>C++,concurrentqueue(高性能并发队列)

    项目中的数据队列基于轮询和selep的实时性及CUP性能差,需要进行优化,尝试使用concurrentqueue进行优化.网上有一些资料介绍,可供参考. 使用后的个人理解:一个线程安全的queue,并 ...

  4. 客户资料查询传递数据格式

    客户资料查询传递数据格式 大家好! 客户资料查询字段JSON格式如下(附件为数据文件): [ { "colName":"CUSTTEL", "colT ...

  5. 隔年增长的题_行测资料分析:一起聊聊隔年增长

    隔年增长在行测资料分析考试中属于一种高频考点,但同时在很多考生看来也是一个难点.之所以觉得它是难点,那是因为在隔年增长考查中,很多考生觉得无论是列式还是运算相对都比较难.这样让有些甚至对于公式都不熟悉 ...

  6. GitHub:TensorFlow、PyTorch最全资料集锦

    给各位小伙伴们推出几个深度学习框架的资料集锦,统一命名为:XXX-From-Zero-To-One.下面po一幅深度学习框架发展的重要历史点: 从上图可知,TensorFlow和PyTorch是目前深 ...

  7. 【camera】自动泊车-视觉车位检测相关资料汇总(论文、数据集、源代码、相关博客、演示demo)(1)

    [camera]自动泊车-视觉车位检测相关资料汇总(论文.数据集.源代码.相关博客.演示demo)parking slot detection 论文 2020论文 2019论文 2018论文 2017 ...

  8. 【radar】毫米波雷达静态障碍物识别及其相关资料(仿真、生成、标定、运动估计、静态障碍物识别)(3)

    [radar]毫米波雷达相关资料(毫米波仿真.毫米波生成模型.毫米波标定.毫米波运动估计.毫米波静态障碍物识别)(3) 毫米波标定:多毫米波雷达联合标定.相机和毫米波雷达联合标定.毫米波雷达和激光雷达 ...

  9. 【radar】毫米波雷达相关资料(文献综述列表、顶会研讨会资料列表、顶会workshops资料列表、工具书、使用手册)(2)

    [radar]毫米波雷达相关资料(毫米波雷达文献综述列表.毫米波雷达顶会研讨会资料列表.毫米波雷达顶会workshops资料列表.毫米波雷达工具书.毫米波雷达使用手册)(2) Review Paper ...

最新文章

  1. MySQL Replace INTO的使用
  2. 前端学习(2780):创建项目和外观
  3. 《构建之法》阅读笔记01
  4. python3.6安装ipython_centos6.5下安装python3.6、pip、ipython
  5. hashtable资料
  6. 无线通信基础(一):高斯随机变量
  7. wps 项目进度_wps excle做甘特图|如何利用excel自动生成施工进度计划横道图
  8. 常用服务和开放端口对照表
  9. PyTorch 全部笔记的思维导图精简记忆版
  10. js下载activex
  11. 有感于《成都私车数量超上海》
  12. 转 C++异常机制的实现方式和开销分析 白杨 http://baiy.cn
  13. html实体注册商标,html 注册商标,html 注册商标代码
  14. Merkle Tree(默克尔树)原理解析
  15. Android 系统自动获取来电/短信/提示铃声
  16. 【HTTP Live Streaming】(一)苹果公司 - 流媒体传输技术 - 概览
  17. js 翻转表格(行列互换)
  18. IE浏览器确定兼容性模式
  19. 新学期,新FLAG | 从心出发
  20. photoshop邮票制作教程

热门文章

  1. 常见的Transforms的使用方法
  2. 皮卡丘(pikachu) 文件上传
  3. 海航重组当当,航机飘上书香
  4. 电脑出现Hold Escape key to prevent StartlsBack from loading,导致电脑闪屏。
  5. Python进行微信公众号开发
  6. chromedriver与chrome浏览器各版本对应下载
  7. 华栖云联手阿里云创首个媒体公共云平台
  8. word表批量处理小技巧(python+宏)
  9. 【深度学习】步态识别-论文阅读:(T-PAMI-2021)综述:Deep Gait Recognition
  10. dva如何去掉hash