improved hazard pointer support, still has issues though

This commit is contained in:
Dominik Tomičević 2015-11-21 22:48:56 +01:00
parent 12628f3689
commit a0a4b0bf40
7 changed files with 363 additions and 0 deletions

77
examples/hazard_ptrs.cpp Normal file
View File

@ -0,0 +1,77 @@
#include <iostream>
#include <mutex>
#include <vector>
#include <chrono>
#include "threading/thread.hpp"
#include "threading/hazard_ptr.hpp"
std::mutex mutex;
struct Foo
{
int bar = 0;
};
void scan_foos(const std::vector<Foo>& foos)
{
auto& hp = HazardStore::get();
std::unique_lock<std::mutex> cout_guard(mutex);
std::cout << "Scanning foos..." << std::endl;
for(auto& foo : foos)
{
auto foo_ptr = &foo;
std::unique_lock<std::mutex> cout_guard(mutex);
std::cout << "Foo taken? " << hp.scan(foo_ptr) << std::endl;
}
}
int main(void)
{
using namespace std::chrono_literals;
std::cout << std::boolalpha;
static constexpr size_t NUM_THREADS = 8;
std::vector<Thread> threads;
std::vector<Foo> foos;
foos.resize(NUM_THREADS + 2);
for(size_t i = 0; i < NUM_THREADS; ++i)
threads.emplace_back([&foos]() {
auto id = this_thread::id;
auto foo = &foos.at(id);
auto hazard = hazard_ptr(foo);
foo->bar = id;
std::unique_lock<std::mutex> cout_guard(mutex);
std::cout << "Hello from thread " << this_thread::id << std::endl;
std::this_thread::sleep_for(5s);
});
// 0 to NUM_THREADS foos should be taken
// maybe none, maybe all!
scan_foos(foos);
std::this_thread::sleep_for(3s);
// first NUM_THREADS foos should be taken
scan_foos(foos);
std::this_thread::sleep_for(3s);
// all foos should be available now
scan_foos(foos);
for(auto& thread : threads)
thread.join();
return 0;
}

BIN
examples/skiplist Executable file

Binary file not shown.

10
examples/skiplist.cpp Normal file
View File

@ -0,0 +1,10 @@
#include <iostream>
#include "data_structures/skiplist/skiplist.hpp"
int main(void)
{
auto skiplist = new SkipList<int, int>();
return 0;
}

123
threading/hazard_ptr.hpp Normal file
View File

@ -0,0 +1,123 @@
#pragma once
#include <atomic>
#include <vector>
#include <unistd.h>
#include "hazard_store.hpp"
class hazard_ptr
{
static constexpr size_t EMPTY = -1;
static constexpr uintptr_t NULLPTR = 0;
public:
hazard_ptr() = default;
template <class T>
hazard_ptr(const T* ptr) : ptr(reinterpret_cast<uintptr_t>(ptr))
{
if(ptr == nullptr)
return;
idx = HazardStore::get().acquire(this->ptr);
}
hazard_ptr(const hazard_ptr&) = delete;
hazard_ptr(hazard_ptr&& other)
{
*this = std::move(other);
}
~hazard_ptr()
{
reset();
}
void reset()
{
if(idx == EMPTY)
return;
HazardStore::get().release(idx);
detach();
}
hazard_ptr& operator=(hazard_ptr&& other)
{
reset();
ptr = other.ptr;
idx = other.idx;
other.detach();
return *this;
}
uintptr_t get() const
{
return ptr;
}
template <class T>
operator T*() const
{
return reinterpret_cast<T*>(ptr);
}
friend bool operator==(const hazard_ptr& lhs, uintptr_t rhs)
{
return lhs.ptr == rhs;
}
friend bool operator==(uintptr_t lhs, const hazard_ptr& rhs)
{
return operator==(rhs, lhs);
}
template <class T>
friend bool operator==(const hazard_ptr& lhs, const T* const rhs)
{
return lhs.ptr == reinterpret_cast<uintptr_t>(rhs);
}
template <class T>
friend bool operator==(const T* const lhs, const hazard_ptr& rhs)
{
return operator==(rhs, lhs);
}
friend bool operator!=(const hazard_ptr& lhs, uintptr_t rhs)
{
return !operator==(lhs, rhs);
}
friend bool operator!=(uintptr_t lhs, const hazard_ptr& rhs)
{
return operator!=(rhs, lhs);
}
template <class T>
friend bool operator!=(const hazard_ptr& lhs, const T* const rhs)
{
return !operator==(lhs, rhs);
}
template <class T>
friend bool operator!=(const T* const lhs, const hazard_ptr& rhs)
{
return operator!=(rhs, lhs);
}
private:
uintptr_t ptr {NULLPTR};
size_t idx {EMPTY};
void detach()
{
ptr = NULLPTR;
idx = EMPTY;
}
};

View File

@ -0,0 +1,93 @@
#pragma once
#include <cstdlib>
#include <vector>
#include <atomic>
#include <cassert>
#include <thread>
#include "id.hpp"
class HazardPointerError : std::runtime_error
{
using runtime_error::runtime_error;
};
class HazardStore
{
using atomic_hp_t = std::atomic<uintptr_t>;
static constexpr uintptr_t NULLPTR = 0;
friend class hazard_ptr;
HazardStore(size_t N, size_t K) : N(N), K(K),
ptrs(new atomic_hp_t[N * K]) {}
public:
HazardStore(const HazardStore&) = delete;
HazardStore(HazardStore&&) = delete;
HazardStore& operator=(const HazardStore&) = delete;
static HazardStore& get()
{
static constexpr size_t N = 16; // number of threds
static constexpr size_t K = 128; // pointers per thread
static HazardStore hp(N, K);
return hp;
}
template <class T>
bool scan(T* ptr)
{
return scan(reinterpret_cast<uintptr_t>(ptr));
}
bool scan(uintptr_t ptr)
{
assert(ptr != NULLPTR);
for(size_t i = 0; i < N * K; ++i)
{
auto& hazard = ptrs[i];
if(hazard == ptr)
return true;
}
return false;
}
private:
const size_t N, K;
std::unique_ptr<atomic_hp_t[]> ptrs;
size_t acquire(uintptr_t ptr)
{
assert(ptr != NULLPTR);
auto idx = this_thread::id;
for(auto i = N * idx; i < N * idx + K; ++i)
{
auto& hazard = ptrs[i];
if(hazard.load(std::memory_order_relaxed) == NULLPTR)
continue;
// this MUST be seq_cst, otherwise garbage collector might not see
// the hazard pointer even if it is set
hazard.store(ptr, std::memory_order_seq_cst);
return i;
}
throw HazardPointerError("Exhausted all hazard pointers");
}
void release(size_t idx)
{
assert(ptrs[idx] != NULLPTR);
ptrs[idx].store(NULLPTR, std::memory_order_release);
}
};

6
threading/id.hpp Normal file
View File

@ -0,0 +1,6 @@
#pragma once
namespace this_thread
{
thread_local unsigned id = 0;
};

54
threading/thread.hpp Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <atomic>
#include <thread>
#include <cassert>
#include "utils/underlying_cast.hpp"
#include "id.hpp"
class Thread
{
static std::atomic<unsigned> thread_counter;
public:
static size_t count(std::memory_order order = std::memory_order_seq_cst)
{
return thread_counter.load(order);
}
static constexpr unsigned UNINITIALIZED = -1;
static constexpr unsigned MAIN_THREAD = 0;
template <class F>
Thread(F f)
{
thread_id = thread_counter.fetch_add(1, std::memory_order_acq_rel);
thread = std::thread([this, f]() { start_thread(f); });
}
Thread() = default;
Thread(const Thread&) = delete;
Thread(Thread&& other)
{
assert(thread_id == UNINITIALIZED);
thread_id = other.thread_id;
thread = std::move(other.thread);
}
void join() { return thread.join(); }
private:
unsigned thread_id = UNINITIALIZED;
std::thread thread;
template <class F, class... Args>
void start_thread(F&& f)
{
this_thread::id = thread_id;
f();
}
};
std::atomic<unsigned> Thread::thread_counter {1};