Commit d793c0c6 by Christian Kern

Merge branch 'development' into embb_520_prepare_valuepool_for_herlihy_object_pool

parents 51938714 b9182d3d
......@@ -128,6 +128,20 @@ void embb_internal_thread_index_set_max(unsigned int max) {
*embb_max_number_thread_indices() = max;
}
/**
* \pre the calling thread is the only active thread
*
* \post the thread indices count and calling thread index is reset
*/
void embb_internal_thread_index_reset() {
/** This function is only called in tests, usually when all other threads
* except the main thread have terminated. However, the main thread still has
* potentially stored its old index value in its thread local storage,
* which might be assigned additionally to another thread (as the counter is
* reset), which may lead to hard to detect bugs. Therefore, reset the thread
* local thread id here.
*/
embb_internal_thread_index_var = UINT_MAX;
embb_counter_init(embb_thread_index_counter());
}
}
\ No newline at end of file
......@@ -77,7 +77,12 @@ LockFreeMPMCQueue<Type, ValuePool>::~LockFreeMPMCQueue() {
template< typename Type, typename ValuePool >
LockFreeMPMCQueue<Type, ValuePool>::LockFreeMPMCQueue(size_t capacity) :
capacity(capacity),
capacity(capacity),
// Object pool, size with respect to the maximum number of retired nodes not
// eligible for reuse. +1 for dummy node.
objectPool(
MPMCQueueNodeHazardPointer_t::ComputeMaximumRetiredObjectCount(2) +
capacity + 1),
// Disable "this is used in base member initializer" warning.
// We explicitly want this.
#ifdef EMBB_PLATFORM_COMPILER_MSVC
......@@ -89,13 +94,7 @@ delete_pointer_callback(*this,
#ifdef EMBB_PLATFORM_COMPILER_MSVC
#pragma warning(pop)
#endif
hazardPointer(delete_pointer_callback, NULL, 2),
// Object pool, size with respect to the maximum number of retired nodes not
// eligible for reuse. +1 for dummy node.
objectPool(
hazardPointer.GetRetiredListMaxSize()*
embb::base::Thread::GetThreadsMaxCount() +
capacity + 1) {
hazardPointer(delete_pointer_callback, NULL, 2) {
// Allocate dummy node to reduce the number of special cases to consider.
internal::LockFreeMPMCQueueNode<Type>* dummyNode = objectPool.Allocate();
// Initially, head and tail point to the dummy node.
......@@ -120,7 +119,7 @@ bool LockFreeMPMCQueue<Type, ValuePool>::TryEnqueue(Type const& element) {
for (;;) {
my_tail = tail;
hazardPointer.GuardPointer(0, my_tail);
hazardPointer.Guard(0, my_tail);
// Check if pointer is still valid after guarding.
if (my_tail != tail) {
......@@ -163,12 +162,12 @@ bool LockFreeMPMCQueue<Type, ValuePool>::TryDequeue(Type & element) {
Type data;
for (;;) {
my_head = head;
hazardPointer.GuardPointer(0, my_head);
hazardPointer.Guard(0, my_head);
if (my_head != head) continue;
my_tail = tail;
my_next = my_head->GetNext();
hazardPointer.GuardPointer(1, my_next);
hazardPointer.Guard(1, my_next);
if (head != my_head) continue;
if (my_next == NULL)
......@@ -187,7 +186,7 @@ bool LockFreeMPMCQueue<Type, ValuePool>::TryDequeue(Type & element) {
break;
}
hazardPointer.EnqueuePointerForDeletion(my_head);
hazardPointer.EnqueueForDeletion(my_head);
element = data;
return true;
}
......
......@@ -81,13 +81,12 @@ capacity(capacity),
#ifdef EMBB_PLATFORM_COMPILER_MSVC
#pragma warning(pop)
#endif
hazardPointer(delete_pointer_callback, NULL, 1),
// Object pool, size with respect to the maximum number of retired nodes not
// eligible for reuse:
objectPool(
hazardPointer.GetRetiredListMaxSize()*
embb::base::Thread::GetThreadsMaxCount() +
capacity) {
StackNodeHazardPointer_t::ComputeMaximumRetiredObjectCount(1) +
capacity),
hazardPointer(delete_pointer_callback, NULL, 1) {
}
template< typename Type, typename ValuePool >
......@@ -128,7 +127,7 @@ bool LockFreeStack< Type, ValuePool >::TryPop(Type & element) {
return false;
// Guard top_cached
hazardPointer.GuardPointer(0, top_cached);
hazardPointer.Guard(0, top_cached);
// Check if top is still top. If this is the case, it has not been
// retired yet (because before retiring that thing, the retiring thread
......@@ -144,16 +143,16 @@ bool LockFreeStack< Type, ValuePool >::TryPop(Type & element) {
break;
} else {
// We continue with the next and can unguard top_cached
hazardPointer.GuardPointer(0, NULL);
hazardPointer.Guard(0, NULL);
}
}
Type data = top_cached->GetElement();
// We don't need to read from this reference anymore, unguard it
hazardPointer.GuardPointer(0, NULL);
hazardPointer.Guard(0, NULL);
hazardPointer.EnqueuePointerForDeletion(top_cached);
hazardPointer.EnqueueForDeletion(top_cached);
element = data;
return true;
......
......@@ -113,8 +113,17 @@ class LockFreeMPMCQueue {
* least as many elements, maybe more.
*/
size_t capacity;
// Do not change the ordering of class local variables.
// Important for initialization.
/**
* The object pool, used for lock-free memory allocation.
*
* Warning: the objectPool has to be initialized before the hazardPointer
* object, to be sure that the hazardPointer object is destructed before the
* Pool as the hazardPointer object might return elements to the pool in its
* destructor. So the ordering of the members objectPool and hazardPointer is
* important here!
*/
ObjectPool< internal::LockFreeMPMCQueueNode<Type>, ValuePool > objectPool;
/**
* Callback to the method that is called by hazard pointers if a pointer is
......@@ -124,15 +133,17 @@ class LockFreeMPMCQueue {
delete_pointer_callback;
/**
* The hazard pointer object, used for memory management.
* Definition of the used hazard pointer type
*/
embb::containers::internal::HazardPointer
< internal::LockFreeMPMCQueueNode<Type>* > hazardPointer;
typedef embb::containers::internal::HazardPointer
< internal::LockFreeMPMCQueueNode<Type>* >
MPMCQueueNodeHazardPointer_t;
/**
* The object pool, used for lock-free memory allocation.
* The hazard pointer object, used for memory management.
*/
ObjectPool< internal::LockFreeMPMCQueueNode<Type>, ValuePool > objectPool;
MPMCQueueNodeHazardPointer_t hazardPointer;
/**
* Atomic pointer to the head node of the queue
......
......@@ -187,11 +187,6 @@ class LockFreeStack {
delete_pointer_callback;
/**
* The hazard pointer object, used for memory management.
*/
internal::HazardPointer<internal::LockFreeStackNode<Type>*> hazardPointer;
/**
* The callback function, used to cleanup non-hazardous pointers.
* \see delete_pointer_callback
*/
......@@ -199,10 +194,27 @@ class LockFreeStack {
/**
* The object pool, used for lock-free memory allocation.
*
* Warning: the objectPool has to be initialized before the hazardPointer
* object, to be sure that the hazardPointer object is destructed before the
* Pool as the hazardPointer object might return elements to the pool in its
* destructor. So the ordering of the members objectPool and hazardPointer is
* important here!
*/
ObjectPool< internal::LockFreeStackNode<Type>, ValuePool > objectPool;
/**
* Definition of the used hazard pointer type
*/
typedef internal::HazardPointer < internal::LockFreeStackNode<Type>* >
StackNodeHazardPointer_t;
/**
* The hazard pointer object, used for memory management.
*/
StackNodeHazardPointer_t hazardPointer;
/**
* Atomic pointer to the top node of the stack (element that is popped next)
*/
embb::base::Atomic<internal::LockFreeStackNode<Type>*> top;
......
......@@ -36,32 +36,112 @@
namespace embb {
namespace containers {
namespace test {
class HazardPointerTest : public partest::TestCase {
/**
* @brief a very simple wait-free object pool implementation to have tests
* being independent of the EMBB object pool implementation.
*/
class IntObjectTestPool {
private:
embb::base::Function<void, embb::base::Atomic<int>*> delete_pointer_callback;
int* simplePoolObjects;
embb::base::Atomic<int>* simplePool;
//used to allocate random stuff, we will just use the pointers, not the
//contents
embb::containers::ObjectPool< embb::base::Atomic<int> >* object_pool;
public:
static const int ALLOCATED_MARKER = 1;
static const int FREE_MARKER = 0;
unsigned int poolSize;
//used to move pointer between threads
embb::containers::LockFreeStack< embb::base::Atomic<int>* >* stack;
embb::base::Mutex vector_mutex;
embb::containers::internal::HazardPointer<embb::base::Atomic<int>*>* hp;
std::vector< embb::base::Atomic<int>* > deleted_vector;
int n_threads;
int n_elements_per_thread;
int n_elements;
explicit IntObjectTestPool(unsigned int pool_size);
~IntObjectTestPool();
/**
* Allocate object from the pool
*
* @return the allocated object
*/
int* Allocate();
/**
* Return an element to the pool
*
* @param objectPointer the object to be freed
*/
void Release(int* object_pointer);
};
class HazardPointerTest : public partest::TestCase {
public:
/**
* Adds test methods.
*/
HazardPointerTest();
void HazardPointerTest1_Pre();
void HazardPointerTest1_Post();
void HazardPointerTest1_ThreadMethod();
void HazardPointerTest1Pre();
void HazardPointerTest1Post();
void HazardPointerTest1ThreadMethod();
void DeletePointerCallback(embb::base::Atomic<int>* to_delete);
private:
embb::base::Function<void, embb::base::Atomic<int>*> delete_pointer_callback_;
//used to allocate random stuff, we will just use the pointers, not the
//contents
embb::containers::ObjectPool< embb::base::Atomic<int> >* object_pool_;
//used to move pointer between threads
embb::containers::LockFreeStack< embb::base::Atomic<int>* >* stack_;
embb::base::Mutex vector_mutex_;
embb::containers::internal::HazardPointer<embb::base::Atomic<int>*>*
hazard_pointer_;
std::vector< embb::base::Atomic<int>* > deleted_vector_;
int n_threads_;
int n_elements_per_thread_;
int n_elements_;
};
class HazardPointerTest2 : public partest::TestCase {
public:
void DeletePointerCallback(int* to_delete);
bool SetRelativeGuards();
void HazardPointerTest2Master();
void HazardPointerTest2Slave();
void HazardPointerTest2Pre();
void HazardPointerTest2Post();
void HazardPointerTest2ThreadMethod();
HazardPointerTest2();
private:
// number of threads, participating in that test
int n_threads;
embb::base::Function<void, int*> delete_pointer_callback_;
// the thread id of the master
embb::base::Atomic<unsigned int> current_master_;
// variables, to synchronize threads. At each point in time, one master,
// the master changes each round until each thread was assigned master once.
embb::base::Atomic<int> sync1_;
embb::base::Atomic<unsigned int> sync2_;
unsigned int guards_per_phread_count_;
unsigned int guaranteed_capacity_pool_;
unsigned int pool_size_using_hazard_pointer_;
// The threads write here, if they guarded an object successfully. Used to
// determine when all allocated objects were guarded successfully.
embb::base::Atomic<int*>* shared_guarded_;
// This array is used by the master, to communicate and share what he has
// allocated with the slaves.
embb::base::Atomic<int*>* shared_allocated_;
// Reference to the object pool
IntObjectTestPool* test_pool_;
embb::containers::internal::HazardPointer<int*>* hazard_pointer_;
static const int FINISH_MARKER = -1;
};
} // namespace test
} // namespace containers
......
......@@ -55,6 +55,7 @@ using embb::containers::test::HazardPointerTest;
using embb::containers::test::QueueTest;
using embb::containers::test::StackTest;
using embb::containers::test::ObjectPoolTest;
using embb::containers::test::HazardPointerTest2;
PT_MAIN("Data Structures C++") {
unsigned int max_threads = static_cast<unsigned int>(
......@@ -64,6 +65,7 @@ PT_MAIN("Data Structures C++") {
PT_RUN(PoolTest< WaitFreeArrayValuePool<int COMMA -1> >);
PT_RUN(PoolTest< LockFreeTreeValuePool<int COMMA -1> >);
PT_RUN(HazardPointerTest);
PT_RUN(HazardPointerTest2);
PT_RUN(QueueTest< WaitFreeSPSCQueue< ::std::pair<size_t COMMA int> > >);
PT_RUN(QueueTest< LockFreeMPMCQueue< ::std::pair<size_t COMMA int> >
COMMA true COMMA true >);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment