folly无锁队列正确性说明
folly无锁队列是facebook开源的一个无所队列,使用的是单向链表,通过compare_exchange语句实现的多生产多消费的队列,我曾经花了比较多的时间学习memory_order的说明,对release-acquire语义,自认为还是比较了解。如果一个atomic对象使用std::memory_order_release进行写操作,而另外一个线程使用std::memory_order_acquire进行读操作,那么这两个线程之间形成同步关系。std::memory_order_release之前写的效果,在std::memory_order_acquire之后可见。不过对于多生产多消费模型,存在多个生产者的情况,在有多个生产者的情况下,结果正确吗?
这里给出folly的源代码,这里请重点关注insertHead函数和sweepOnce函数。
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ #pragma once #include <atomic>
#include <cassert>
#include <utility> namespace folly { /**
* A very simple atomic single-linked list primitive.
*
* Usage:
*
* class MyClass {
* AtomicIntrusiveLinkedListHook<MyClass> hook_;
* }
*
* AtomicIntrusiveLinkedList<MyClass, &MyClass::hook_> list;
* list.insert(&a);
* list.sweep([] (MyClass* c) { doSomething(c); }
*/
template <class T>
struct AtomicIntrusiveLinkedListHook {
T* next{ nullptr };
}; template <class T, AtomicIntrusiveLinkedListHook<T> T::*HookMember>
class AtomicIntrusiveLinkedList {
public:
AtomicIntrusiveLinkedList() {}
AtomicIntrusiveLinkedList(const AtomicIntrusiveLinkedList&) = delete;
AtomicIntrusiveLinkedList& operator=(const AtomicIntrusiveLinkedList&) =
delete;
AtomicIntrusiveLinkedList(AtomicIntrusiveLinkedList&& other) noexcept {
auto tmp = other.head_.load();
other.head_ = head_.load();
head_ = tmp;
}
AtomicIntrusiveLinkedList& operator=(
AtomicIntrusiveLinkedList&& other) noexcept {
auto tmp = other.head_.load();
other.head_ = head_.load();
head_ = tmp; return *this;
} /**
* Note: list must be empty on destruction.
*/
~AtomicIntrusiveLinkedList() {
assert(empty());
} bool empty() const {
return head_.load() == nullptr;
} /**
* Atomically insert t at the head of the list.
* @return True if the inserted element is the only one in the list
* after the call.
*/
bool insertHead(T* t) {
assert(next(t) == nullptr); auto oldHead = head_.load(std::memory_order_relaxed);
do {
next(t) = oldHead;
/* oldHead is updated by the call below.
NOTE: we don't use next(t) instead of oldHead directly due to
compiler bugs (GCC prior to 4.8.3 (bug 60272), clang (bug 18899),
MSVC (bug 819819); source:
http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange */
} while (!head_.compare_exchange_weak(oldHead, t,
std::memory_order_release,
std::memory_order_relaxed)); return oldHead == nullptr;
} /**
* Replaces the head with nullptr,
* and calls func() on the removed elements in the order from tail to head.
* Returns false if the list was empty.
*/
template <typename F>
bool sweepOnce(F&& func) {
if (auto head = head_.exchange(nullptr)) {
auto rhead = reverse(head);
unlinkAll(rhead, std::forward<F>(func));
return true;
}
return false;
}/**
* Repeatedly replaces the head with nullptr,
* and calls func() on the removed elements in the order from tail to head.
* Stops when the list is empty.
*/
template <typename F>
void sweep(F&& func) {
while (sweepOnce(func)) {
}
} /**
* Similar to sweep() but calls func() on elements in LIFO order.
*
* func() is called for all elements in the list at the moment
* reverseSweep() is called. Unlike sweep() it does not loop to ensure the
* list is empty at some point after the last invocation. This way callers
* can reason about the ordering: elements inserted since the last call to
* reverseSweep() will be provided in LIFO order.
*
* Example: if elements are inserted in the order 1-2-3, the callback is
* invoked 3-2-1. If the callback moves elements onto a stack, popping off
* the stack will produce the original insertion order 1-2-3.
*/
template <typename F>
void reverseSweep(F&& func) {
// We don't loop like sweep() does because the overall order of callbacks
// would be strand-wise LIFO which is meaningless to callers.
auto head = head_.exchange(nullptr);
unlinkAll(head, std::forward<F>(func));
} private:
std::atomic<T*> head_{ nullptr }; static T*& next(T* t) {
return (t->*HookMember).next;
} /* Reverses a linked list, returning the pointer to the new head
(old tail) */
static T* reverse(T* head) {
T* rhead = nullptr;
while (head != nullptr) {
auto t = head;
head = next(t);
next(t) = rhead;
rhead = t;
}
return rhead;
} /* Unlinks all elements in the linked list fragment pointed to by `head',
* calling func() on every element */
template <typename F>
void unlinkAll(T* head, F&& func) {
while (head != nullptr) {
auto t = head;
head = next(t);
next(t) = nullptr;
func(t);
}
}
}; } // namespace folly
如果存在两个线程先后向同一个队列中插入节点,由于两个线程中没有一个使用acquire,如果仅按照release-acquire语义,显然,正确性无法保证,后一个insertHead函数中,无论是auto oldHead = head_.load(std::memory_order_relaxed);,还是while (!head_.compare_exchange_weak(oldHead, t, std::memory_order_release,std::memory_order_relaxed));都可能读取的是前一个线程插入前的数据。那么,还有什么C++语义,可以保证folly队列的正确性?那就是release sequence。release sequence其中的一部分说的是:
如果一个存储使用memory_order_release或更严格的内存序,后面跟着若干读-改-写(read-modify-write)(可以是同一个线程,也可以是不同的线程)操作的话。
(1)那么中间的读-改-写操作 读取的要么是前一次读-改-写的结果,要么是存储的数据。
那么,如果存在一个release操作,后面跟着一个读改写操作的话,这个读改写操作肯定会得到之前release操作写入的效果。我们可以观察到insertHead中的compare_exchange_weak为一个release操作,同时也是一个读改写操作,那么前面一个线程的修改,一定会在后面一个compare_exchange_weak中可见,无论是同一个线程调用,还是不同线程调用。注意到auto oldHead = head_.load(std::memory_order_relaxed);得到的结果的正确性与否,不影响compare_exchange_weak的正确性,因为如果前一个读取的结果是旧值,这个操作就会失败,而且将oldHead的值更新为最新值,这点对于理解folly的正确性很重要。其他的情况应该根据类似的原理得到正确的解答,这里就不详细说明了。
folly无锁队列正确性说明的更多相关文章
- folly无锁队列,尝试添加新的函数(续)
基于上一篇文章,dropHead取出节点后,删除节点,会出现内存访问的问题.按照这个逻辑,如果将移出的节点保存到一个无锁队列中,然后在需要节点的时候,从这个备用的无锁队列中取出节点,那么应该就可以避开 ...
- folly无锁队列,尝试添加新的函数
1. folly是facebook开源的关于无锁队列的库,实现过程很精妙.folly向队列中添加节点过程,符合标准库中的队列的设计,而取出节点的过程,则会造成多个线程的分配不均.我曾经试着提供一次 取 ...
- 基于folly的AtomicIntrusiveLinkedList无锁队列进行简单封装的多生产多消费模型
1.基于folly的AtomicIntrusiveLinkedList略微修改的无锁队列代码: #ifndef FOLLY_REVISE_H #define FOLLY_REVISE_H namesp ...
- readerwriterqueue 一个用 C++ 实现的快速无锁队列
https://www.oschina.net/translate/a-fast-lock-free-queue-for-cpp?cmp&p=2 A single-producer, sing ...
- 无锁队列以及ABA问题
队列是我们非常常用的数据结构,用来提供数据的写入和读取功能,而且通常在不同线程之间作为数据通信的桥梁.不过在将无锁队列的算法之前,需要先了解一下CAS(compare and swap)的原理.由于多 ...
- HashMap的原理与实 无锁队列的实现Java HashMap的死循环 red black tree
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html https://zh.wikipedia.org/wiki/%E7%BA ...
- zeromq源码分析笔记之无锁队列ypipe_t(3)
在上一篇中说到了mailbox_t的底层实际上使用了管道ypipe_t来存储命令.而ypipe_t实质上是一个无锁队列,其底层使用了yqueue_t队列,ypipe_t是对yueue_t的再包装,所以 ...
- boost 无锁队列
一哥们翻译的boost的无锁队列的官方文档 原文地址:http://blog.csdn.net/great3779/article/details/8765103 Boost_1_53_0终于迎来了久 ...
- 一个可无限伸缩且无ABA问题的无锁队列
关于无锁队列,详细的介绍请参考陈硕先生的<无锁队列的实现>一文.然进一步,如何实现一个不限node数目即能够无限伸缩的无锁队列,即是本文的要旨. 无锁队列有两种实现形式,分别是数组与链表. ...
随机推荐
- (22)Ajax的基本使用(实现登录功能和局部刷新以及防止跨站请求伪造攻击)
Ajax的作用 前后端分离的项目,需要交互,就要通过Ajax来完成交互 AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”.即 ...
- 两道dp
链接:https://ac.nowcoder.com/acm/contest/186/C?&headNav=www 来源:牛客网终于Alice走出了大魔王的陷阱,可是现在傻傻的她忘了带武器了, ...
- html2canvas用法的总结(转载)
最近做h5网页,有个功能是用户能长按页面保存为图片,在我们理解就是网页要生成图片然后再让用户长按保存,然后就发现了html2canvas这个框架了,效果挺不错了,但是有几个坑说一下(用的最新版): h ...
- What's New In Zeebe: Scaling Zeebe, New Client APIs, Faster Requests, Timestamps, NodeJS Client, and Default Topic is Back!
Written by Daniel Meyer on May 16 2018 in the What's New In Zeebe category. Welcome to the first-eve ...
- Creating Node.js Command Line Utilities to Improve Your Workflow
转自:https://developer.telerik.com/featured/creating-node-js-command-line-utilities-improve-workflow/ ...
- Gource 方便的软件版本可视化录制工具
Gource 是一个特别棒的软件变更可视化录制工具,我们可以使用此工具,方便的将软件的版本变动,录制 为视频 安装 brew install gource brew install ffmpeg ...
- C# to IL 1 Introduction to Microsoft’s IL(MSIL 介绍)
The code that we write in a programming language like C#, ASP+ or in any other .NETcompatible langua ...
- 持续集成--Jenkins--1
持续集成之Jenkins安装部署 1.安装JDK Jenkins是Java编写的,所以需要先安装JDK,这里采用yum安装,如果对版本有需求,可以直接在Oracle官网下载JDK. [root@l ...
- 家庭记账本web开发
这个系统的整体结构: GitHub:https://github.com/lq1998lq/Test.git com.action包: package com.action; import java. ...
- Chrome 66 禁止声音自动播放
声音无法自动播放一直在IOS/Android上面都是一个惯例, 桌面端的 Safari在2017年的11版本中也宣布禁止带有声音的多媒体自动播放, 紧接着2018年4月份Chrome发布的66版本也正 ...