improved hazard pointer support, still has issues though
This commit is contained in:
parent
12628f3689
commit
a0a4b0bf40
77
examples/hazard_ptrs.cpp
Normal file
77
examples/hazard_ptrs.cpp
Normal 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
BIN
examples/skiplist
Executable file
Binary file not shown.
10
examples/skiplist.cpp
Normal file
10
examples/skiplist.cpp
Normal 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
123
threading/hazard_ptr.hpp
Normal 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;
|
||||
}
|
||||
};
|
93
threading/hazard_store.hpp
Normal file
93
threading/hazard_store.hpp
Normal 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
6
threading/id.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace this_thread
|
||||
{
|
||||
thread_local unsigned id = 0;
|
||||
};
|
54
threading/thread.hpp
Normal file
54
threading/thread.hpp
Normal 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};
|
Loading…
Reference in New Issue
Block a user