1. folly是facebook开源的关于无锁队列的库,实现过程很精妙。folly向队列中添加节点过程,符合标准库中的队列的设计,而取出节点的过程,则会造成多个线程的分配不均。我曾经试着提供一次 取出一个节点的函数,虽然存在一些问题,不过还是有很多可以学习的地方。我新增的函数,在下面代码中,会在注释中标识“新增函数”。

/*
* 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;
} // 新增函数
// if std::memory_order_acquire applies to next(oldHead)(the first one, the argument of compare_exchange_weak)
// and I don't know if following bugs affect the code
// GCC prior to 4.8.3 (bug 60272), clang prior to 2014-05-05 (bug 18899)
// MSVC prior to 2014-03-17 (bug 819819).
template <typename F>
bool sweepHead(F&& func)
{
// handle if the list is not empty
auto oldHead = head_.load(std::memory_order_relaxed); while (oldHead != nullptr &&
!head_.compare_exchange_weak(oldHead, next(oldHead),
std::memory_order_acquire, std::memory_order_relaxed))
;
// if drop out head successfully
if (oldHead)
{
next(oldHead) = nullptr;
unlinkAll(oldHead, std::forward<F>(func));
return true;
} return false;
} // 新增函数
// if std::memory_order_acquire does not apply to next(oldHead)
// and I don't know if following bugs affect the code
// GCC prior to 4.8.3 (bug 60272), clang prior to 2014-05-05 (bug 18899)
// MSVC prior to 2014-03-17 (bug 819819).
template <typename F>
bool dropHead(F&& func)
{
T* oldHead = nullptr;
// handle if the list is not empty
while ((oldHead = head_.load(std::memory_order_acquire)))
{
// because insert and drop out will be involving with head_, they
// will change head_ first, then others
bool res = head_.compare_exchange_weak(oldHead, next(oldHead), std::memory_order_relaxed,
std::memory_order_relaxed);
if (res/* && oldHead != nullptr*/)
{
next(oldHead) = nullptr;
unlinkAll(oldHead, 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

下面是我测试时使用的代码:

// Test.cpp : 定义控制台应用程序的入口点。
// #include <memory>
#include <cassert> #include <iostream>
#include <vector>
#include <thread>
#include <future>
#include <random>
#include <cmath> #include "folly.h" using namespace folly; struct student_name
{
//student_name(const std::string& name)
// : name(name)
//{ //} //std::string name;
student_name(int age = )
: age(age)
{ } int age;
AtomicIntrusiveLinkedListHook<student_name> node;
}; AtomicIntrusiveLinkedList<student_name, &student_name::node> g_students; std::atomic<int> g_inserts; // insert num (successful)
std::atomic<int> g_drops; // drop num (successful) std::atomic<int> g_printNum; // as same as g_drops std::atomic<long> g_ageInSum; // age sum when producing student_name
std::atomic<long> g_ageOutSum; // age sum when consuming student_name std::atomic<bool> goOn(true); constexpr int ONE_THREAD_PRODUCE_NUM = ; // when testing, no more than this number, you know 20,000,00 * 100 * 10~= MAX_INT constexpr int PRODUCE_THREAD_NUM = ; // producing thread number
constexpr int CONSUME_THREAD_NUM = ; // consuming thread number void printOne(student_name* t)
{
g_printNum.fetch_add(, std::memory_order_relaxed);
g_ageOutSum.fetch_add(t->age, std::memory_order_relaxed);
// use memory_order_relaxed avoiding affect folly memory order
g_drops.fetch_add(, std::memory_order_relaxed);
// clean node
// delete t;
} void insert_students()
{
std::default_random_engine dre(time(nullptr));
std::uniform_int_distribution<int> ageDi(, ); for (int i = ; i != ONE_THREAD_PRODUCE_NUM; ++i)
{
int newAge = ageDi(dre);
g_ageInSum.fetch_add(newAge, std::memory_order_relaxed);
g_students.insertHead(new student_name(newAge));
// use memory_order_relaxed avoiding affect folly memory order
g_inserts.fetch_add(, std::memory_order_relaxed);
}
} void drop_students()
{
while (goOn.load(std::memory_order_relaxed))
{
g_students.dropHead(printOne);
}
} int main()
{
std::vector<std::future<void>> insert_threads;
for (int i = ; i != PRODUCE_THREAD_NUM; ++i)
{
insert_threads.push_back(std::async(insert_students));
} std::vector<std::future<void>> drop_threads;
for (int i = ; i != CONSUME_THREAD_NUM; ++i)
{
drop_threads.push_back(std::async(drop_students));
} for (auto& item : insert_threads)
{
item.get();
} goOn.store(std::memory_order_relaxed); for (auto& item : drop_threads)
{
item.get();
} g_students.sweep(printOne); std::cout << "insert count1: " << g_inserts.load() << std::endl;
std::cout << "drop count1: " << g_drops.load() << std::endl;
std::cout << "print num1: " << g_printNum.load() << std::endl; std::cout << "age in1: " << g_ageInSum.load() << std::endl;
std::cout << "age out1: " << g_ageOutSum.load() << std::endl; std::cout << std::endl;
}

我将我测试中的主要要点说一下:

(1)以上代码,将printOne函数中的// delete t;前面的注释符号(“//”)去掉。我在ubuntu测试结果正常。

(2)如果将main函数中的insert_threads.push_back(std::async(insert_students, i));改为insert_threads.push_back(std::async(std::launch::async, insert_students, i));,将main函数中的drop_threads.push_back(std::async(drop_students, i));改为drop_threads.push_back(std::async(std::launch::async, drop_students, i));,将printOne函数中的// delete t;前面的注释符号(“//”)去掉。在ubuntu下运行,会出现内存访问的问题。(这个在多线程测试时值得注意)

(3)如果将(2)中的delete t注释掉,则不会出现内存访问的问题。

(4)如果PRODUCE_THTREAD_NUM依旧为10,而CONSUME_THREAD_NUM改为1,也不会出现内存访问的问题。

(5)如果PRODUCE_THTREAD_NUM为1,而CONSUME_THREAD_NUM依旧为9,会出现内存访问的问题。

(6)修改PRODUCE_THTREAD_NUM和PRODUCE_THTREAD_NUM,只要不是1改成非1,非1改成1,对结果没有影响。

(7)如果大幅度减小HANDLE_NUM,例如改为20000,在PRODUCE_THREAD_NUM为10,CONSUME_THREAD_NUM也没有出现内存访问的问题。

说明:

由测试结果表明,新增函数(两个函数测试效果相同)存在问题,不能适用于多线程取出。问题的原因,经过分析,应该是head_.compare_exchange_weak(oldHead, next(oldHead), std::memory_order_relaxed, std::memory_order_relaxed);(对于dropHead)和delete t之间的冲突,如果在该函数调用之前,对应节点已经被删除,则会出现内存访问的问题,在我看来,对于compare_exchange_weak失败的情况,如果不用分析next(oldHead),则没有任何问题,也就是说如果compare_exchange_weak使用类似于&&或者||的短路设计方式的话,以上代码可以正常运行。但是compare_exchange_weak的实现,为标准库提供,不能修改,所以如果想要使用以上的函数,则需要考虑以上的冲突。

2. 我之前尝试写上面的函数的目的是为了将节点均匀分配到不同的线程。我有一个设想, 没有实际的代码,各位可以参考一下,如果觉得可用,可以考虑实现。

dropHead的实现步骤如下:

(1)使用folly的sweepOnce函数,一次取出所有的节点。

(2)判断sweepOnce中的head是否为nullptr,next(head)是否为nullptr,如果head不为nullptr,而且next(head)不为nullptr,则将_head与next(head)交换。

(3)将next(head)指向的节点队列添加到_head指向的无锁队列中,处理head指向的节点。

以上的代码,因为(2)中的操作步骤很少(两个判断),所以(3)中的插入应该也会很少。所以新增函数的负担应该不大,甚至可以考虑(3)中的节点不再进行回插,总的来说,应该会使每个线程的处理量更加均匀。不过,考虑到线程可能会在(2)中被中断,所以建议进行认真测试的情况下再使用。

如果有什么疑问,或者有什么好的想法,希望能够告诉我。

folly无锁队列,尝试添加新的函数的更多相关文章

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

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

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

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

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

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

  4. boost 无锁队列

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

  5. zeromq源码分析笔记之无锁队列ypipe_t(3)

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

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

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

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

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

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

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

  9. DPDK 无锁队列Ring Library原理(学习笔记)

    参考自DPDK官方文档原文:http://doc.dpdk.org/guides-20.02/prog_guide/ring_lib.html 针对自己的理解做了一些辅助解释. 1 前置知识 1.1 ...

随机推荐

  1. 尚硅谷【SpringBoot】入门

    https://www.bilibili.com/video/av20965295/?p=2 缺点: 基于springframe的封装    对framework api需要熟悉 2微服务 2014 ...

  2. MySQL Lock--并发插入导致的死锁

    ============================================================================ 测试脚本: 表结构: CREATE TABLE ...

  3. js中获取当前url参数值的一个方法

    var $_GET = (function(){             var url = window.document.location.href.toString();//获得当前url地址并 ...

  4. eclipse 视图打不开解决方法

    遇到一个eclipse问题,查看方法调用者,或打开调用层次窗口失败,这时要查看一个方法的调用者只好通过全局搜索的方式.网上搜索报错关键词没找到答案,看了一下全局设置也没有想过的选项. 后想到一个ecl ...

  5. 串、串的模式匹配算法(子串查找)BF算法、KMP算法

    串的定长顺序存储#define MAXSTRLEN 255,//超出这个长度则超出部分被舍去,称为截断 串的模式匹配: 串的定义:0个或多个字符组成的有限序列S = 'a1a2a3…….an ' n ...

  6. 如果指针为空,返回ERROR

    if(!p) //是!p而不是p return ERROR;

  7. monkey如何获取app包名

    别人学习网址:http://www.51testing.com/html/58/15092658-2984032.html 使用aapt    aapt是sdk自带的一个工具,在sdk\builds- ...

  8. C# 控件

    .ascx:Web窗体用户控件.用来存放独立的用户控件,可提供众多页面使用: <%@ Control Language="C#" AutoEventWireup=" ...

  9. golang 如何查看channel通道中未读数据的长度

    可以通过内建函数len查看channel中元素的个数. 内建函数len的定义如下: func len(v Type) int The len built-in function returns the ...

  10. 大数据时代——为什么用HADOOP?

    转载自:http://www.daniubiji.cn/archives/538 什么叫大数据 “大”,说的并不仅是数据的“多”!不能用数据到了多少TB ,多少PB 来说. 对于大数据,可以用四个词来 ...