parallel/stack: Add a trivial thread safe stack
This commit is contained in:
parent
4b3e04ccd6
commit
e033cb2e4f
@ -407,6 +407,8 @@ list (
|
||||
memory/deleter.hpp
|
||||
parallel/queue.cpp
|
||||
parallel/queue.hpp
|
||||
parallel/stack.cpp
|
||||
parallel/stack.hpp
|
||||
parse/time.cpp
|
||||
parse/time.hpp
|
||||
parse/value.cpp
|
||||
@ -631,6 +633,7 @@ if (TESTS)
|
||||
matrix
|
||||
memory/deleter
|
||||
parallel/queue
|
||||
parallel/stack
|
||||
parse/value
|
||||
parse/time
|
||||
parse/si
|
||||
|
9
parallel/stack.cpp
Normal file
9
parallel/stack.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#include "stack.hpp"
|
89
parallel/stack.hpp
Normal file
89
parallel/stack.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../thread/primitive.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../thread/spinlock.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
namespace cruft::parallel {
|
||||
/// A trivial thread safe stack with a fixed capacity.
|
||||
///
|
||||
/// This implementation uses a spinlock, so don't try to push large
|
||||
/// objects under high contention. In the future it may be replaced with a
|
||||
/// different locking mechanism.
|
||||
template <typename ValueT>
|
||||
class stack {
|
||||
public:
|
||||
stack (std::size_t capacity)
|
||||
: m_cursor (0)
|
||||
, m_store (capacity)
|
||||
{
|
||||
CHECK_GT (capacity, 0u);
|
||||
}
|
||||
|
||||
|
||||
/// Move the value from the top of the stack into an output pointer.
|
||||
///
|
||||
/// The internal object will be destroyed after the output has been set.
|
||||
///
|
||||
/// The value `true` will be returned if the value was successfully
|
||||
/// copied. If no object is available for popping, or locking failed,
|
||||
/// the value `false` will be returned.
|
||||
///
|
||||
/// NOTE: There are no exception guarantees at this time.
|
||||
bool pop (ValueT *out)
|
||||
{
|
||||
std::lock_guard lk (m_lock);
|
||||
if (m_cursor == 0)
|
||||
return false;
|
||||
|
||||
auto ptr = reinterpret_cast<ValueT*> (&m_store[--m_cursor]);
|
||||
*out = std::move (*ptr);
|
||||
ptr->~ValueT ();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// Constructs a ValueT on the top of the stack.
|
||||
///
|
||||
/// The value `true` will be returned if the object was successfully
|
||||
/// stored. In the event of a lack of capacity, locking failure, or
|
||||
/// other error the value `false` will be returned and the user may
|
||||
/// retry the operation.
|
||||
///
|
||||
/// NOTE: There are no exception guarantees at this time.
|
||||
template <typename InputT>
|
||||
bool push (InputT &&arg)
|
||||
{
|
||||
std::lock_guard lk (m_lock);
|
||||
if (m_cursor >= m_store.size ())
|
||||
return false;
|
||||
|
||||
auto ptr = reinterpret_cast<ValueT *> (&m_store[m_cursor++]);
|
||||
new (ptr) ValueT (std::forward<InputT> (arg));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
using index_type = std::size_t;
|
||||
using raw_type = std::aligned_storage_t<sizeof(ValueT), alignof(ValueT)>;
|
||||
|
||||
cruft::thread::spinlock m_lock;
|
||||
std::atomic<index_type> m_cursor;
|
||||
std::vector<raw_type> m_store;
|
||||
};
|
||||
}
|
73
test/parallel/stack.cpp
Normal file
73
test/parallel/stack.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "thread/flag.hpp"
|
||||
#include "parallel/stack.hpp"
|
||||
|
||||
#include "tap.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
|
||||
int main ()
|
||||
{
|
||||
cruft::TAP::logger tap;
|
||||
|
||||
{
|
||||
// Ensure trivial success/failure notifications work
|
||||
cruft::parallel::stack<int> values (1);
|
||||
tap.expect_eq (values.push (0), true, "uncontested empty push succeeds");
|
||||
tap.expect_eq (values.push (0), false, "uncontested full push fails");
|
||||
|
||||
int out;
|
||||
tap.expect_eq (values.pop (&out), true, "uncontested full pop succeeds");
|
||||
|
||||
// Use a nullptr to test this case so that we know if the output
|
||||
// variable is dereferenced before the capacity is tested.
|
||||
tap.expect_eq (values.pop (nullptr), false, "uncontested empty pop fails");
|
||||
}
|
||||
|
||||
{
|
||||
static constexpr int SIZE = 4;
|
||||
cruft::parallel::stack<int> values (SIZE);
|
||||
for (int i = 0; i < SIZE; ++i)
|
||||
values.push (i);
|
||||
|
||||
bool success = true;
|
||||
for (int i = SIZE - 1; i >= 0; --i) {
|
||||
int res = -1;
|
||||
values.pop (&res);
|
||||
|
||||
success = success && res == i;
|
||||
}
|
||||
|
||||
tap.expect (success, "simple popped value sequence matches expected");
|
||||
}
|
||||
|
||||
{
|
||||
auto fight = [] (cruft::parallel::stack<int> &store, cruft::thread::flag &ev, int iterations) {
|
||||
ev.wait ();
|
||||
|
||||
while (iterations--) {
|
||||
while (!store.push (iterations))
|
||||
;
|
||||
|
||||
for (int res; !store.pop (&res); )
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
static int constexpr ITERATIONS = 8 * 1024;
|
||||
cruft::parallel::stack<int> store (8);
|
||||
cruft::thread::flag ev;
|
||||
std::vector<std::thread> contestants;
|
||||
for (unsigned i = 0; i < std::thread::hardware_concurrency () + 1; ++i)
|
||||
contestants.emplace_back (fight, std::ref (store), std::ref (ev), ITERATIONS);
|
||||
|
||||
ev.notify_all ();
|
||||
|
||||
for (auto &t: contestants)
|
||||
t.join ();
|
||||
|
||||
tap.expect (true, "n-way fight, %! contestants", contestants.size ());
|
||||
}
|
||||
|
||||
return tap.status ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user