Lockfree list, initial implementation of the remove method
This commit is contained in:
parent
52a6ea0c58
commit
1d0d4b746c
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
77
test/lockfree_list.cpp
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user