Lockfree list, initial implementation of the remove method

This commit is contained in:
Marko Budiselic 2015-10-18 22:15:06 +02:00
parent 52a6ea0c58
commit 1d0d4b746c
3 changed files with 165 additions and 15 deletions
data_structures/list
memory
test

View File

@ -5,6 +5,7 @@
#include <unistd.h>
#include "threading/sync/lockable.hpp"
#include "memory/hp.hpp"
namespace lockfree
{
@ -23,7 +24,18 @@ public:
class read_iterator
{
public:
read_iterator(T* curr) : curr(curr) {}
// constructor
read_iterator(T* curr) :
curr(curr),
hazard_ref(std::move(memory::HP::get().insert(curr))) {}
// no copy constructor
read_iterator(read_iterator& other) = delete;
// move constructor
read_iterator(read_iterator&& other) :
curr(other.curr),
hazard_ref(std::move(other.hazard_ref)) {}
T& operator*() { return *curr; }
T* operator->() { return curr; }
@ -32,8 +44,8 @@ public:
read_iterator& operator++()
{
// todo add curr->next to the hazard pointer list
// (synchronization with GC)
auto& hp = memory::HP::get();
hazard_ref = std::move(hp.insert(curr->next.load()));
curr = curr->next.load();
return *this;
@ -46,6 +58,7 @@ public:
private:
T* curr;
memory::HP::reference hazard_ref;
};
class read_write_iterator
@ -53,8 +66,20 @@ public:
friend class List<T, sleep_time>;
public:
read_write_iterator(T* prev, T* curr) : prev(prev), curr(curr) {}
read_write_iterator(T* prev, T* curr) :
prev(prev),
curr(curr),
hazard_ref(std::move(memory::HP::get().insert(curr))) {}
// no copy constructor
read_write_iterator(read_write_iterator& other) = delete;
// move constructor
read_write_iterator(read_write_iterator&& other) :
prev(other.prev),
curr(other.curr),
hazard_ref(std::move(other.hazard_ref)) {}
T& operator*() { return *curr; }
T* operator->() { return curr; }
@ -62,8 +87,8 @@ public:
read_write_iterator& operator++()
{
// todo add curr->next to the hazard pointer list
// (synchronization with GC)
auto& hp = memory::HP::get();
hazard_ref = std::move(hp.insert(curr->next.load()));
prev = curr;
curr = curr->next.load();
@ -76,7 +101,9 @@ public:
}
private:
T* prev, curr;
T* prev;
T* curr;
memory::HP::reference hazard_ref;
};
read_iterator begin()
@ -149,15 +176,17 @@ public:
// is that this node will move further down the list next time the
// garbage collector traverses this list and therefore it will become
// deletable
if(it->prev == nullptr)
if(it.prev == nullptr) {
std::cout << "prev null" << std::endl;
return false;
}
// HEAD --> ... --> [i] --> [i + 1] --> [i + 2] --> ...
//
// prev curr next
auto prev = it->prev;
auto curr = it->curr;
auto prev = it.prev;
auto curr = it.curr;
auto next = curr->next.load(std::memory_order_acquire);
// effectively remove the curr node from the list
@ -171,14 +200,19 @@ public:
prev->next.store(next, std::memory_order_release);
// TODO curr is now removed from the list so no iterators will be able
// curr is now removed from the list so no iterators will be able
// to reach it at this point, but we still need to check the hazard
// pointers and wait until everyone who currently holds a reference to
// it has stopped using it before we can physically delete it
// while(hp.find(reinterpret_cast<uintptr_t>(curr)))
// sleep(sleep_time);
// TODO: test more appropriate
auto& hp = memory::HP::get();
while(hp.find(reinterpret_cast<uintptr_t>(curr)))
sleep(sleep_time);
delete curr;
return true;
}

View File

@ -4,6 +4,7 @@
#include <atomic>
#include <cassert>
#include <unistd.h>
#include <iostream>
namespace memory
{
@ -44,6 +45,11 @@ public:
// hazard pointer is cleared once reference goes out of scope
~reference()
{
// TODO: remove
// std::cout << "reference destructor called: ";
// std::cout << this->idx;
// std::cout << std::endl;
// check if this reference was moved during its lifetime
if(idx < 0)
return;
@ -52,6 +58,11 @@ public:
hp.clear(*this);
}
reference& operator=(reference&& other)
{
return *this;
}
private:
reference(int64_t idx) : idx(idx) {}
int64_t idx;
@ -92,6 +103,34 @@ public:
}
}
bool find(uintptr_t hptr)
{
for (size_t i = 0; i < HP_SIZE; ++i) {
auto& hptr_i = ptr_list[i];
if (hptr_i != hptr)
continue;
if (hptr_i.load() == 1)
return true;
if (hptr_i.load() == 0)
return false;
}
return false;
}
friend std::ostream& operator<<(std::ostream& os, const HP& hp)
{
os << "Hazard pointers: ";
for (size_t i = 0; i < HP_SIZE; ++i) {
auto& hptr_i = hp.ptr_list[i];
os << hptr_i.load() << " ";
}
return os << std::endl;
}
private:
HP()
{

77
test/lockfree_list.cpp Normal file
View File

@ -0,0 +1,77 @@
#include <iostream>
#include <memory>
#include "memory/hp.hpp"
#include "data_structures/list/lockfree_list.hpp"
#include "mvcc/atom.hpp"
using namespace std;
template <class T>
class ListNode {
public:
ListNode(T value) : value(value) {}
std::atomic<T> value;
std::atomic<ListNode<T>*> prev;
std::atomic<ListNode<T>*> next;
};
using lf_list = lockfree::List<ListNode<int>>;
using sptr_listnode = std::shared_ptr<lf_list>;
void test(sptr_listnode& list)
{
auto& hp = memory::HP::get();
auto head = list->begin();
cout << hp;
cout << "Element: " << (*head).value << endl;
++head;
cout << hp;
cout << "Element: " << (*head).value << endl;
}
void print_list(sptr_listnode& list)
{
cout << "Lockfree list: ";
auto head = list->begin();
while (head->next != nullptr) {
cout << (*head).value << " ";
++head;
}
cout << (*head).value << " ";
cout << endl;
}
int main()
{
auto& hp = memory::HP::get();
sptr_listnode list(new lf_list());
list->push_front(new ListNode<int>(1));
list->push_front(new ListNode<int>(2));
list->push_front(new ListNode<int>(3));
cout << "Initial list" << endl;
print_list(list);
test(list);
cout << "After test method" << endl;
// remove element from the list
auto rw_head = list->rw_begin();
++rw_head;
++rw_head;
list->remove(rw_head);
cout << "After remove method" << endl;
cout << "Final list" << endl;
print_list(list);
cout << "Final HP list (before main exit, some hp references may still exist)" << endl;
cout << hp;
return 0;
}