Implemented lockfree list and added unit test for it.

This commit is contained in:
Kruno Tomola Fabro 2016-08-03 10:27:18 +01:00
parent e4238d5871
commit 36019f561b
2 changed files with 166 additions and 39 deletions

View File

@ -1,39 +1,41 @@
#pragma once #pragma once
#include "utils/crtp.hpp"
#include <atomic> #include <atomic>
template <class T> template <class T>
static T *load(std::atomic<T *> &atomic) static T load(std::atomic<T> &atomic)
{ {
return atomic.load(std::memory_order_acquire()) return atomic.load(std::memory_order_acquire);
} }
template <class T> template <class T>
static void store(std::atomic<T *> &atomic, T *desired) static void store(std::atomic<T> &atomic, T desired)
{ // Maybe could be relaxed { // Maybe could be relaxed
atomic.store(desired, std::memory_order_release()); atomic.store(desired, std::memory_order_release);
} }
template <class T> template <class T>
static bool cas(std::atomic<T *> &atomic, T *expected, T *desired) static bool cas(std::atomic<T> &atomic, T expected, T desired)
{ // Could be relaxed must be atleast Release. { // Could be relaxed must be atleast Release.
return atomic.compare_exchange_strong(expected,desired,std::memory_order_seq_cst())); return atomic.compare_exchange_strong(expected, desired,
std::memory_order_seq_cst);
} }
template <class T> template <class T>
static T *swap(std::atomic<T *> &atomic, T *desired) static T *swap(std::atomic<T *> &atomic, T *desired)
{ // Could be relaxed { // Could be relaxed
return atomic.exchange(desired,std::memory_order_seq_cst())); return atomic.exchange(desired, std::memory_order_seq_cst);
} }
template <class T> template <class T>
class List class List
{ {
class Node
{
friend class Iterator;
private: private:
class Node
{
public:
Node(const T &data) : data(data) {} Node(const T &data) : data(data) {}
Node(T &&data) : data(std::forward(data)) {} Node(T &&data) : data(std::forward(data)) {}
@ -43,40 +45,43 @@ class List
std::atomic<bool> removed{false}; std::atomic<bool> removed{false};
}; };
class Iterator template <class It>
class IteratorBase : public Crtp<It>
{ {
friend class List; friend class List;
Iterator() : list(nullptr), curr(nullptr) {} protected:
IteratorBase() : list(nullptr), curr(nullptr) {}
Iterator(List *list) : list(list) IteratorBase(List *list) : list(list)
{ {
list->count++; list->count++;
reset(); reset();
} }
public: public:
Iterator(const Accessor &) = delete; IteratorBase(const IteratorBase &) = delete;
Iterator(Iterator &&other) IteratorBase(IteratorBase &&other)
: list(other.list), curr(other.curr), prev(other.prev) : list(other.list), curr(other.curr), prev(other.prev)
{ {
other.list = nullptr; other.list = nullptr;
other.curr = nullptr; other.curr = nullptr;
} }
~Iterator() ~IteratorBase()
{ {
if (list == nullptr) { if (list == nullptr) {
return; return;
} }
auto head_rem = load(list->head_rem); auto head_rem = load(list->removed);
// Fetch could be relaxed // Fetch could be relaxed
// There exist possibility that no one will delete garbage at this // There exist possibility that no one will delete garbage at this
// time. // time.
if (list.count.fetch_sub(1) == 1 && head_rem != nullptr && if (list->count.fetch_sub(1) == 1 && head_rem != nullptr &&
cas(list->head_rem, head_rem, cas<Node *>(
list->removed, head_rem,
nullptr)) { // I am the last one and there is garbage to be nullptr)) { // I am the last one and there is garbage to be
// removed. // removed.
auto now = head_rem; auto now = head_rem;
@ -91,30 +96,32 @@ class List
T &operator*() T &operator*()
{ {
assert(valid()); assert(valid());
return *curr; return curr->data;
} }
T *operator->() T *operator->()
{ {
assert(valid()); assert(valid());
return curr; return curr->data;
} }
operator T *() operator T *()
{ {
assert(valid()); assert(valid());
return curr; return curr->data;
} }
bool valid() { return curr != nullptr; } bool valid() { return curr != nullptr; }
Iterator &operator++() // Iterating is wait free.
It &operator++()
{ {
assert(valid()); assert(valid());
do { do {
prev = curr; prev = curr;
curr = load(curr->next); curr = load(curr->next);
} while (valid() && is_removed()); } while (valid() && is_removed());
return this; return this->derived();
} }
It &operator++(int) { return operator++(); }
bool is_removed() bool is_removed()
{ {
@ -122,28 +129,36 @@ class List
return load(curr->removed); return load(curr->removed);
} }
// Returns iterator to begining // Returns IteratorBase to begining
void reset() void reset()
{ {
prev = nullptr; prev = nullptr;
curr = load(list->head); curr = load(list->head);
while (valid() && is_removed()) { while (valid() && is_removed()) {
this ++; operator++();
} }
} }
// Adds to the begining of list // Adds to the begining of list
// It is lock free but it isn't wait free.
void push(T &&data) void push(T &&data)
{ {
auto node = new Node(data); auto node = new Node(data);
auto next = nullptr; Node *next = nullptr;
do { do {
next = load(list->head); next = load(list->head);
store(next.next, next); store(node->next, next);
} while (!cas(list->head, next, node)); } while (!cas(list->head, next, node));
} }
// True only if this call removed the element. // True only if this call removed the element. Only reason for fail is
// if
// the element is already removed.
// Remove has deadlock if another thread dies between marking node for
// removal
// and the disconnection.
// This can be improved with combinig the removed flag with prev.next or
// curr.next
bool remove() bool remove()
{ {
assert(valid()); assert(valid());
@ -157,28 +172,25 @@ class List
return false; return false;
} }
friend bool operator==(const Iterator &a, const Iterator &b) friend bool operator==(const It &a, const It &b)
{ {
return a->curr == b->curr; return a.curr == b.curr;
} }
friend bool operator!=(const Iterator &a, const Iterator &b) friend bool operator!=(const It &a, const It &b) { return !(a == b); }
{
return !(a == b);
}
private: private:
void find_and_disconnect() void find_and_disconnect()
{ {
auto it = Iterator(list); auto it = It(list);
auto next = load(curr->next); auto next = load(curr->next);
while (it.valid()) { while (it.valid()) {
if (it.succ == succ) { if (it.curr == curr) {
if (it.disconnect()) { if (it.disconnect()) {
return; return;
} }
it.reset(); it.reset();
} else if (it.succ == next) { // Comparison with next is } else if (it.curr == next) { // Comparison with next is
// optimization for early return. // optimization for early return.
return; return;
} else { } else {
@ -206,6 +218,38 @@ class List
Node *curr; Node *curr;
}; };
public:
class ConstIterator : public IteratorBase<ConstIterator>
{
friend class List;
public:
using IteratorBase<ConstIterator>::IteratorBase;
const T &operator*()
{
return IteratorBase<ConstIterator>::operator*();
}
const T *operator->()
{
return IteratorBase<ConstIterator>::operator->();
}
operator const T &()
{
return IteratorBase<ConstIterator>::operator T &();
}
};
class Iterator : public IteratorBase<Iterator>
{
friend class List;
public:
using IteratorBase<Iterator>::IteratorBase;
};
public: public:
List() = default; List() = default;
@ -216,10 +260,18 @@ public:
Iterator begin() { return Iterator(this); } Iterator begin() { return Iterator(this); }
ConstIterator begin() const { return ConstIterator(this); }
ConstIterator cbegin() const { return ConstIterator(this); }
Iterator end() { return Iterator(); } Iterator end() { return Iterator(); }
ConstIterator end() const { return ConstIterator(); }
ConstIterator cend() const { return ConstIterator(); }
private: private:
std::atomic<size_t> count{0}; std::atomic<size_t> count{0};
std::atomic<Node *> head{nullptr}; std::atomic<Node *> head{nullptr};
std::atomic<Node *> removed{nullptr}; std::atomic<Node *> removed{nullptr};
} };

View File

@ -0,0 +1,75 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "data_structures/list/lockfree_list_new.hpp"
TEST_CASE("Conncurent List insert")
{
List<int> list;
auto it = list.begin();
it.push(32);
it.reset();
REQUIRE(*it == 32);
}
TEST_CASE("Conncurent List iterate")
{
List<int> list;
auto it = list.begin();
it.push(32);
it.push(7);
it.push(9);
it.push(0);
it.reset();
REQUIRE(*it == 0);
it++;
REQUIRE(*it == 9);
it++;
REQUIRE(*it == 7);
it++;
REQUIRE(*it == 32);
it++;
REQUIRE(it == list.end());
}
TEST_CASE("Conncurent List head remove")
{
List<int> list;
auto it = list.begin();
it.push(32);
it.reset();
REQUIRE(it.remove());
REQUIRE(it.is_removed());
REQUIRE(!it.remove());
it.reset();
REQUIRE(it == list.end());
}
TEST_CASE("Conncurent List remove")
{
List<int> list;
auto it = list.begin();
it.push(32);
it.push(7);
it.push(9);
it.push(0);
it.reset();
it++;
it++;
REQUIRE(it.remove());
REQUIRE(it.is_removed());
REQUIRE(!it.remove());
it.reset();
REQUIRE(*it == 0);
it++;
REQUIRE(*it == 9);
it++;
REQUIRE(*it == 32);
it++;
REQUIRE(it == list.end());
}