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无锁队列正确性说明的更多相关文章

  1. folly无锁队列,尝试添加新的函数(续)

    基于上一篇文章,dropHead取出节点后,删除节点,会出现内存访问的问题.按照这个逻辑,如果将移出的节点保存到一个无锁队列中,然后在需要节点的时候,从这个备用的无锁队列中取出节点,那么应该就可以避开 ...

  2. folly无锁队列,尝试添加新的函数

    1. folly是facebook开源的关于无锁队列的库,实现过程很精妙.folly向队列中添加节点过程,符合标准库中的队列的设计,而取出节点的过程,则会造成多个线程的分配不均.我曾经试着提供一次 取 ...

  3. 基于folly的AtomicIntrusiveLinkedList无锁队列进行简单封装的多生产多消费模型

    1.基于folly的AtomicIntrusiveLinkedList略微修改的无锁队列代码: #ifndef FOLLY_REVISE_H #define FOLLY_REVISE_H namesp ...

  4. readerwriterqueue 一个用 C++ 实现的快速无锁队列

    https://www.oschina.net/translate/a-fast-lock-free-queue-for-cpp?cmp&p=2 A single-producer, sing ...

  5. 无锁队列以及ABA问题

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

  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. zeromq源码分析笔记之无锁队列ypipe_t(3)

    在上一篇中说到了mailbox_t的底层实际上使用了管道ypipe_t来存储命令.而ypipe_t实质上是一个无锁队列,其底层使用了yqueue_t队列,ypipe_t是对yueue_t的再包装,所以 ...

  8. boost 无锁队列

    一哥们翻译的boost的无锁队列的官方文档 原文地址:http://blog.csdn.net/great3779/article/details/8765103 Boost_1_53_0终于迎来了久 ...

  9. 一个可无限伸缩且无ABA问题的无锁队列

    关于无锁队列,详细的介绍请参考陈硕先生的<无锁队列的实现>一文.然进一步,如何实现一个不限node数目即能够无限伸缩的无锁队列,即是本文的要旨. 无锁队列有两种实现形式,分别是数组与链表. ...

随机推荐

  1. python-基础-文件

    一.文件操作 打开文件时,需要指定文件路径和以何等方式打开文件, 对文件操作流程 打开文件,得到文件句柄并赋值给一个变量 通过句柄对文件进行操作 关闭文件 打开文件的模式有: r ,只读模式[默认模式 ...

  2. easyui的select使用

    这是一个小demo,其他相关功能查看easyuiAPI,需要注意的是valueField: 'id', textField: 'name'中id指json的key,name指json的值,id和nam ...

  3. Json工具类JsonUtil

    import com.alibaba.fastjson.JSONArray; import com.fasterxml.jackson.core.JsonProcessingException; im ...

  4. Vue.js系列之项目搭建

    项目搭建具体步骤如下: 1.安装node (中)https://nodejs.org/zh-cn/ (英)https://nodejs.org/en/ 2.安装cnpm镜像 (node自带安装了npm ...

  5. linux后台运行之&和nohup区别,模拟后台守护进程

    先来看一下&的使用 root@BP:~# cat test.sh #!/bin/bash while true do echo "linux">/dev/null d ...

  6. ClusterControl docker 环境搭建

    ClusterControl 是一款比较强大的数据库管理平台,包含了丰富的数据库管理功能. 我们可以用来方便的进行数据管理 测试使用docker-compose 管理 环境准备 docker-comp ...

  7. 04基于python玩转人工智能最火框架之TensorFlow开发环境搭建

    MOOC_VM.vdl.zip 解压之后,得到一个vdl文件.打开virtual box,新建选择类型linuxubuntu 64位. 选择继续,分配2g.使用已有的虚拟硬盘文件,点击选择我们下载的文 ...

  8. Unity3D用户手册 学习

  9. 上外网tunnel手段

    需要的软件 1, httptunnel软件,包括服务端和客户端,家里开启服务端,公司开启客户端 2,(可选)proxifier PE,用来在公司check 代理工作是否正常 3,动态域名服务,在家里用 ...

  10. redis之 Redis持久化配置

    Redis持久化配置 Redis的持久化有2种方式   1快照  2是日志 Rdb快照的配置选项 save 900 1      // 900内,有1条写入,则产生快照 save 300 1000   ...