thread: add minimal thread primitives for win32

This allows us to get around the lack of these types under MinGW
This commit is contained in:
Danny Robson 2019-06-22 15:46:34 +10:00
parent eb90d1a239
commit 01094611eb
38 changed files with 654 additions and 90 deletions

View File

@ -12,6 +12,12 @@ find_package (PythonInterp 3 REQUIRED)
find_package (RAGEL 6.9 REQUIRED) find_package (RAGEL 6.9 REQUIRED)
if (WIN32)
# Enable features for Windows Vista and higher
add_definitions("-D_WIN32_WINNT=0x0600")
endif ()
############################################################################### ###############################################################################
include (TestBigEndian) include (TestBigEndian)
TEST_BIG_ENDIAN(ENDIANNESS) TEST_BIG_ENDIAN(ENDIANNESS)
@ -59,7 +65,7 @@ if (BACKTRACE_LIB_FOUND)
list (APPEND UTIL_FILES backtrace_execinfo.cpp) list (APPEND UTIL_FILES backtrace_execinfo.cpp)
elseif (HAVE_CAPTURESTACKBACKTRACE) elseif (HAVE_CAPTURESTACKBACKTRACE)
list (APPEND UTIL_FILES backtrace_stackwalk.cpp) list (APPEND UTIL_FILES backtrace_stackwalk.cpp)
target_link_libraries(cruft DbgHelp) target_link_libraries(cruft dbghelp)
else () else ()
list (APPEND UTIL_FILES backtrace_null.cpp) list (APPEND UTIL_FILES backtrace_null.cpp)
endif () endif ()
@ -98,6 +104,7 @@ list (
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
if (LINUX) if (LINUX)
list (APPEND UTIL_FILES list (APPEND UTIL_FILES
thread/thread_std.hpp
thread/event_futex.cpp thread/event_futex.cpp
thread/flag_futex.cpp thread/flag_futex.cpp
rand/system_linux.cpp rand/system_linux.cpp
@ -141,7 +148,6 @@ if (WIN32)
exe_win32.cpp exe_win32.cpp
io_win32.cpp io_win32.cpp
io_win32.hpp io_win32.hpp
thread/event_win32.cpp
library_win32.cpp library_win32.cpp
library_win32.hpp library_win32.hpp
rand/system_win32.cpp rand/system_win32.cpp
@ -164,35 +170,44 @@ endif ()
############################################################################### ###############################################################################
list (APPEND UTIL_FILES list (APPEND UTIL_FILES
thread/condition_variable.hpp
thread/event.hpp thread/event.hpp
thread/flag.hpp thread/flag.hpp
thread/mutex.hpp
thread/primitive.hpp thread/primitive.hpp
thread/semaphore.hpp thread/semaphore.hpp
thread/thread.hpp
) )
if (LINUX) if (LINUX)
list (APPEND UTIL_FILES list (APPEND UTIL_FILES
thread/condition_variable_std.hpp
thread/event_futex.cpp thread/event_futex.cpp
thread/event_futex.hpp thread/event_futex.hpp
thread/semaphore_linux.hpp thread/semaphore_linux.hpp
thread/semaphore_linux.cpp thread/semaphore_linux.cpp
thread/flag_futex.cpp thread/flag_futex.cpp
thread/flag_futex.hpp thread/flag_futex.hpp
thread/mutex_std.hpp
) )
elseif (WIN32) elseif (WIN32)
list (APPEND UTIL_FILES list (APPEND UTIL_FILES
thread/condition_variable_win32.cpp
thread/condition_variable_win32.hpp
thread/event_std.cpp thread/event_std.cpp
thread/event_std.hpp thread/event_std.hpp
thread/event_win32.cpp
thread/mutex_win32.cpp
thread/mutex_win32.hpp
thread/semaphore_win32.hpp thread/semaphore_win32.hpp
thread/semaphore_win32.cpp thread/semaphore_win32.cpp
thread/flag_std.cpp thread/flag_std.cpp
thread/flag_std.hpp thread/flag_std.hpp
thread/thread_win32.cpp
thread/thread_win32.hpp
) )
else () else ()
list (APPEND UTIL_FILES message (FATAL_ERROR "Unsupported thread platform")
thread/flag_std.cpp
thread/flag_std.hpp
)
endif () endif ()
@ -483,6 +498,7 @@ list (
thread/flag.hpp thread/flag.hpp
thread/monitor.cpp thread/monitor.cpp
thread/monitor.hpp thread/monitor.hpp
thread/mutex.hpp
thread/ticketlock.cpp thread/ticketlock.cpp
thread/ticketlock.hpp thread/ticketlock.hpp
thread/spinlock.cpp thread/spinlock.cpp
@ -681,12 +697,15 @@ if (TESTS)
string string
stringid stringid
strongdef strongdef
thread/condition_variable
thread/event thread/event
thread/flag thread/flag
thread/monitor thread/monitor
thread/mutex
thread/semaphore thread/semaphore
thread/spinlock thread/spinlock
thread/ticketlock thread/ticketlock
thread/thread
time/8601 time/8601
traits traits
tuple/index tuple/index

View File

@ -27,7 +27,7 @@ queue::default_parallelism (void) noexcept
LOG_ERROR ("Unable to parse JOB_THREADS. Using the default"); LOG_ERROR ("Unable to parse JOB_THREADS. Using the default");
} }
return std::thread::hardware_concurrency (); return cruft::thread::thread::hardware_concurrency ();
} }
@ -62,7 +62,7 @@ queue::queue (unsigned thread_count, unsigned pool_size):
CHECK_GE (m_tasks.finishing.capacity (), thread_count); CHECK_GE (m_tasks.finishing.capacity (), thread_count);
for (auto &t: m_threads) for (auto &t: m_threads)
t = std::thread (&queue::loop, this); t = cruft::thread::thread (&queue::loop, this);
} }

View File

@ -11,16 +11,16 @@
#include "../pool.hpp" #include "../pool.hpp"
#include "../thread/ticketlock.hpp"
#include "../thread/semaphore.hpp"
#include "../thread/flag.hpp" #include "../thread/flag.hpp"
#include "../thread/monitor.hpp" #include "../thread/monitor.hpp"
#include "../thread/semaphore.hpp"
#include "../thread/thread.hpp"
#include "../thread/ticketlock.hpp"
#include "../parallel/queue.hpp" #include "../parallel/queue.hpp"
#include <array> #include <array>
#include <deque> #include <deque>
#include <thread>
#include <vector> #include <vector>
#include <new> #include <new>
#include <cstddef> #include <cstddef>
@ -204,10 +204,10 @@ namespace cruft::job {
thread::semaphore m_pending; thread::semaphore m_pending;
std::vector<std::thread> m_threads; std::vector<thread::thread> m_threads;
thread::semaphore m_doomed; thread::semaphore m_doomed;
std::thread m_reaper; thread::thread m_reaper;
}; };
} }

View File

@ -11,7 +11,7 @@
#include "win32/windows.hpp" #include "win32/windows.hpp"
#include <cruft/util/cast.hpp> #include "cast.hpp"
#include <filesystem> #include <filesystem>
#include <libloaderapi.h> #include <libloaderapi.h>

View File

@ -1,5 +1,6 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#include "job/queue.hpp" #include "job/queue.hpp"
#include "thread/thread.hpp"
#include "tap.hpp" #include "tap.hpp"
#include <chrono> #include <chrono>
@ -32,7 +33,7 @@ main (void)
std::atomic<int> count = 0; std::atomic<int> count = 0;
{ {
cruft::job::queue q {std::thread::hardware_concurrency (), INNER}; cruft::job::queue q {cruft::thread::thread::hardware_concurrency (), INNER};
std::vector<cruft::job::queue::cookie> cookies; std::vector<cruft::job::queue::cookie> cookies;
std::generate_n (std::back_inserter (cookies), INNER, [&] () { std::generate_n (std::back_inserter (cookies), INNER, [&] () {
return q.submit ( return q.submit (

View File

@ -1,10 +1,11 @@
#include "parallel/queue.hpp" #include "parallel/queue.hpp"
#include "thread/flag.hpp" #include "thread/flag.hpp"
#include "thread/semaphore.hpp" #include "thread/semaphore.hpp"
#include "thread/thread.hpp"
#include "tap.hpp" #include "tap.hpp"
#include <algorithm> #include <algorithm>
#include <thread>
static constexpr uint32_t slots = 4; static constexpr uint32_t slots = 4;
@ -64,8 +65,8 @@ main ()
// start n consumers, and n producers that fill an array with integers // start n consumers, and n producers that fill an array with integers
std::vector<int> dst (parallelism * chunk_size); std::vector<int> dst (parallelism * chunk_size);
std::vector<std::thread> consumers; std::vector<cruft::thread::thread> consumers;
std::vector<std::thread> producers; std::vector<cruft::thread::thread> producers;
for (int i = 0; i < parallelism; ++i) { for (int i = 0; i < parallelism; ++i) {
consumers.emplace_back ( consumers.emplace_back (

View File

@ -1,9 +1,9 @@
#include "thread/flag.hpp" #include "thread/flag.hpp"
#include "thread/thread.hpp"
#include "parallel/stack.hpp" #include "parallel/stack.hpp"
#include "tap.hpp" #include "tap.hpp"
#include <thread>
int main () int main ()
@ -25,13 +25,13 @@ int main ()
} }
{ {
static constexpr int SIZE = 4; static constexpr int COUNT = 4;
cruft::parallel::stack<int> values (SIZE); cruft::parallel::stack<int> values (COUNT);
for (int i = 0; i < SIZE; ++i) for (int i = 0; i < COUNT; ++i)
values.push (i); values.push (i);
bool success = true; bool success = true;
for (int i = SIZE - 1; i >= 0; --i) { for (int i = COUNT - 1; i >= 0; --i) {
int res = -1; int res = -1;
values.pop (&res); values.pop (&res);
@ -57,8 +57,8 @@ int main ()
static int constexpr ITERATIONS = 8 * 1024; static int constexpr ITERATIONS = 8 * 1024;
cruft::parallel::stack<int> store (8); cruft::parallel::stack<int> store (8);
cruft::thread::flag ev; cruft::thread::flag ev;
std::vector<std::thread> contestants; std::vector<cruft::thread::thread> contestants;
for (unsigned i = 0; i < std::thread::hardware_concurrency () + 1; ++i) for (unsigned i = 0; i < cruft::thread::thread::hardware_concurrency () + 1; ++i)
contestants.emplace_back (fight, std::ref (store), std::ref (ev), ITERATIONS); contestants.emplace_back (fight, std::ref (store), std::ref (ev), ITERATIONS);
ev.notify_all (); ev.notify_all ();

View File

@ -0,0 +1,4 @@
int main ()
{
}

View File

@ -1,8 +1,9 @@
#include "thread/event.hpp" #include "thread/event.hpp"
#include "thread/thread.hpp"
#include "tap.hpp" #include "tap.hpp"
#include <atomic> #include <atomic>
#include <thread>
int int
@ -19,14 +20,14 @@ main ()
std::atomic<int> val = 0; std::atomic<int> val = 0;
cruft::thread::event a; cruft::thread::event a;
std::thread t ([&] () { cruft::thread::thread t ([&] () {
a.wait (); a.wait ();
++val; ++val;
}); });
// block for hopefully long enough to allow the above thread to change // block for hopefully long enough to allow the above thread to change
// the value of the integer. // the value of the integer.
std::this_thread::sleep_for (std::chrono::milliseconds (100)); cruft::thread::this_thread::sleep_for (std::chrono::milliseconds (100));
tap.expect_eq (val, 0, "waiting actually blocks"); tap.expect_eq (val, 0, "waiting actually blocks");

View File

@ -1,7 +1,7 @@
#include "thread/flag.hpp" #include "thread/flag.hpp"
#include "tap.hpp" #include "thread/thread.hpp"
#include <thread> #include "tap.hpp"
int int
@ -12,12 +12,12 @@ main ()
cruft::thread::flag f; cruft::thread::flag f;
std::atomic<int> value = 0; std::atomic<int> value = 0;
std::thread t1 ([&] () { cruft::thread::thread t1 ([&] () {
f.wait (); f.wait ();
value = 1; value = 1;
}); });
std::this_thread::sleep_for (std::chrono::milliseconds (100)); cruft::thread::this_thread::sleep_for (std::chrono::milliseconds (100));
tap.expect_eq (value, 0, "value hasn't been set during wait"); tap.expect_eq (value, 0, "value hasn't been set during wait");
f.notify_all (); f.notify_all ();
@ -25,12 +25,12 @@ main ()
t1.join (); t1.join ();
tap.expect_eq (value, 1, "value has been changed after wait"); tap.expect_eq (value, 1, "value has been changed after wait");
std::thread t2 ([&] () { cruft::thread::thread t2 ([&] () {
f.wait (); f.wait ();
value = 2; value = 2;
}); });
std::this_thread::sleep_for (std::chrono::milliseconds (100)); cruft::thread::this_thread::sleep_for (std::chrono::milliseconds (100));
tap.expect_eq (value, 2, "second wait didn't appear to block"); tap.expect_eq (value, 2, "second wait didn't appear to block");
t2.join (); t2.join ();
@ -59,13 +59,13 @@ main ()
} }
}; };
std::vector<std::thread> workers; std::vector<cruft::thread::thread> workers;
std::generate_n ( std::generate_n (
std::back_inserter (workers), std::back_inserter (workers),
parallelism, parallelism,
[&, i = 0] () mutable [&, i = 0] () mutable
{ {
return std::thread { func, i++ }; return cruft::thread::thread { func, i++ };
}); });
for (auto &t: workers) for (auto &t: workers)

View File

@ -6,13 +6,14 @@
* Copyright 2018 Danny Robson <danny@nerdcruft.net> * Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/ */
#include "tap.hpp"
#include "thread/monitor.hpp"
#include "thread/event.hpp" #include "thread/event.hpp"
#include "thread/monitor.hpp"
#include "thread/semaphore.hpp" #include "thread/semaphore.hpp"
#include "thread/thread.hpp"
#include "tap.hpp"
#include <atomic> #include <atomic>
#include <thread>
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -48,8 +49,8 @@ main ()
auto &enter = obj->enter; auto &enter = obj->enter;
auto &leave = obj->leave; auto &leave = obj->leave;
std::thread t1 ([&] () { obj->inc (); }); cruft::thread::thread t1 ([&] () { obj->inc (); });
std::thread t2 ([&] () { obj->inc (); }); cruft::thread::thread t2 ([&] () { obj->inc (); });
tap.expect_eq (value, 0, "wrapped class was left initialised"); tap.expect_eq (value, 0, "wrapped class was left initialised");

12
test/thread/mutex.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "tap.hpp"
#include "thread/mutex.hpp"
int main ()
{
cruft::TAP::logger tap;
cruft::thread::mutex a;
cruft::thread::mutex b;
return tap.status ();
}

View File

@ -1,9 +1,9 @@
#include "thread/semaphore.hpp" #include "thread/semaphore.hpp"
#include "thread/flag.hpp" #include "thread/flag.hpp"
#include "tap.hpp" #include "thread/thread.hpp"
#include "thread/mutex.hpp"
#include <thread> #include "tap.hpp"
#include <mutex>
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -35,7 +35,7 @@ main ()
// the spawned thread attempts to double acquire a semaphore with // the spawned thread attempts to double acquire a semaphore with
// only one available and so should get blocked immediately. // only one available and so should get blocked immediately.
std::atomic<int> test = 0; std::atomic<int> test = 0;
std::thread t ([&] () { cruft::thread::thread t ([&] () {
sem.lock (); sem.lock ();
sem.lock (); sem.lock ();
test = 1; test = 1;
@ -44,7 +44,7 @@ main ()
// wait until we know the thread should have been blocked. it should // wait until we know the thread should have been blocked. it should
// not have touched the 'test' variable at this point. // not have touched the 'test' variable at this point.
while (sem.value () > 0) while (sem.value () > 0)
std::this_thread::yield (); cruft::thread::this_thread::yield ();
tap.expect_eq (test, 0, "locking blocks in foreign thread"); tap.expect_eq (test, 0, "locking blocks in foreign thread");
@ -56,9 +56,9 @@ main ()
} }
{ {
const auto parallelism = std::thread::hardware_concurrency (); const auto parallelism = cruft::thread::thread::hardware_concurrency ();
constexpr int iterations = 1 << 16; constexpr int iterations = 1 << 16;
std::vector<std::thread> threads; std::vector<cruft::thread::thread> threads;
cruft::thread::semaphore sem (0); cruft::thread::semaphore sem (0);

View File

@ -8,9 +8,10 @@
#include "thread/flag.hpp" #include "thread/flag.hpp"
#include "thread/spinlock.hpp" #include "thread/spinlock.hpp"
#include "thread/thread.hpp"
#include "tap.hpp" #include "tap.hpp"
#include <thread>
#include <mutex> #include <mutex>
@ -37,14 +38,14 @@ main ()
l.unlock (); l.unlock ();
tap.expect (true, "unlocked without contention"); tap.expect (true, "unlocked without contention");
if (std::thread::hardware_concurrency () < 2) { if (cruft::thread::thread::hardware_concurrency () < 2) {
tap.skip ("n-way fight"); tap.skip ("n-way fight");
} else { } else {
constexpr int iterations = 1 << 12; constexpr int iterations = 1 << 12;
cruft::thread::flag start_flag; cruft::thread::flag start_flag;
std::vector<std::thread> contestants; std::vector<cruft::thread::thread> contestants;
for (unsigned i = 0; i < std::thread::hardware_concurrency (); ++i) for (unsigned i = 0; i < cruft::thread::thread::hardware_concurrency (); ++i)
contestants.emplace_back (fight, std::ref (start_flag), std::ref (l), iterations); contestants.emplace_back (fight, std::ref (start_flag), std::ref (l), iterations);
start_flag.notify_all (); start_flag.notify_all ();
@ -52,7 +53,7 @@ main ()
for (auto &t: contestants) for (auto &t: contestants)
t.join (); t.join ();
tap.expect (true, "n-way fight, %! contestants", std::thread::hardware_concurrency ()); tap.expect (true, "n-way fight, %! contestants", cruft::thread::thread::hardware_concurrency ());
} }
return tap.status (); return tap.status ();

29
test/thread/thread.cpp Normal file
View File

@ -0,0 +1,29 @@
/*
* 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 "thread/thread.hpp"
#include "tap.hpp"
#include <atomic>
static void run (std::atomic<int> &val) { ++val; }
int main ()
{
cruft::TAP::logger tap;
// Get the thread to increment an int. If it's been incremented once after
// joining then we assume the thread ran successfully.
std::atomic<int> val = 0;
cruft::thread::thread dude (run, std::ref (val));
dude.join ();
tap.expect_eq (val.load (), 1, "thread appears to have run");
return tap.status ();
}

View File

@ -8,10 +8,10 @@
#include "thread/flag.hpp" #include "thread/flag.hpp"
#include "thread/ticketlock.hpp" #include "thread/ticketlock.hpp"
#include "tap.hpp" #include "thread/thread.hpp"
#include "thread/mutex.hpp"
#include <thread> #include "tap.hpp"
#include <mutex>
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -44,7 +44,7 @@ main ()
l.unlock (); l.unlock ();
tap.expect (true, "unlocked without contention"); tap.expect (true, "unlocked without contention");
if (true || std::thread::hardware_concurrency () < 2) { if (true || cruft::thread::thread::hardware_concurrency () < 2) {
tap.skip ("reasonably fair under contention"); tap.skip ("reasonably fair under contention");
} else { } else {
// measure fairness under contention is below some threshold // measure fairness under contention is below some threshold
@ -63,8 +63,21 @@ main ()
ffs_t::time_point a_finish, b_finish; ffs_t::time_point a_finish, b_finish;
std::thread a (fight, std::ref (start_flag), std::ref (l), iterations, std::ref (a_finish)); cruft::thread::thread a (
std::thread b (fight, std::ref (start_flag), std::ref (l), iterations, std::ref (b_finish)); fight,
std::ref (start_flag),
std::ref (l),
iterations,
std::ref (a_finish)
);
cruft::thread::thread b (
fight,
std::ref (start_flag),
std::ref (l),
iterations,
std::ref (b_finish)
);
auto start = std::chrono::high_resolution_clock::now (); auto start = std::chrono::high_resolution_clock::now ();
start_flag.notify_all (); start_flag.notify_all ();

View File

@ -0,0 +1,19 @@
/*
* 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 "../platform.hpp"
#if defined(PLATFORM_LINUX)
#include "condition_variable_std.hpp"
#elif defined(PLATFORM_WIN32)
#include "condition_variable_win32.hpp"
#else
#error "Unsupported platform for condition_variable"
#endif

View File

@ -0,0 +1,17 @@
/*
* 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 <condition_variable>
namespace cruft::thread {
inline namespace std {
using condition_variable = ::std::condition_variable;
}
}

View File

@ -0,0 +1,42 @@
#include "condition_variable_win32.hpp"
#include <synchapi.h>
using cruft::thread::win32::condition_variable;
///////////////////////////////////////////////////////////////////////////////
condition_variable::condition_variable ()
: m_native (CONDITION_VARIABLE_INIT)
{
InitializeConditionVariable (&m_native);
}
//-----------------------------------------------------------------------------
condition_variable::~condition_variable ()
{ ; }
///////////////////////////////////////////////////////////////////////////////
void
condition_variable::wait (std::unique_lock<cruft::thread::mutex> &lock)
{
SleepConditionVariableCS (
&m_native, &lock.mutex ()->native_handle (), INFINITE
);
}
///////////////////////////////////////////////////////////////////////////////
void condition_variable::notify_one (void) noexcept
{
WakeConditionVariable (&m_native);
}
//-----------------------------------------------------------------------------
void condition_variable::notify_all () noexcept
{
WakeAllConditionVariable (&m_native);
}

View File

@ -0,0 +1,61 @@
/*
* 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 "mutex.hpp"
#include <mutex>
namespace cruft::thread {
inline namespace win32 {
enum class cv_status {
no_timeout,
timeout
};
class condition_variable {
public:
condition_variable ();
condition_variable (condition_variable const&) noexcept = delete;
condition_variable& operator= (condition_variable const&) = delete;
~condition_variable ();
void notify_one (void) noexcept;
void notify_all (void) noexcept;
void wait (std::unique_lock<mutex>&);
template <typename PredicateT>
void wait (
std::unique_lock<mutex> &lock,
PredicateT pred
) {
while (!pred ())
wait (lock);
}
template <class RepT, class PeriodT>
cv_status
wait_for (
std::unique_lock<mutex>& lock,
std::chrono::duration<RepT, PeriodT> const &rel_time
);
template <class RepT, class PeriodT, class PredicateT>
bool wait_for (
std::unique_lock<mutex>& lock,
std::chrono::duration<RepT, PeriodT> const &rel_time,
PredicateT pred
);
private:
CONDITION_VARIABLE m_native;
};
};
}

View File

@ -8,9 +8,10 @@
#pragma once #pragma once
#include "condition_variable.hpp"
#include "mutex.hpp"
#include <atomic> #include <atomic>
#include <mutex>
#include <condition_variable>
namespace cruft::thread { namespace cruft::thread {
@ -48,8 +49,8 @@ namespace cruft::thread {
private: private:
std::atomic<int> m_value; std::atomic<int> m_value;
std::mutex m_mutex; mutex m_mutex;
std::condition_variable m_cv; condition_variable m_cv;
}; };
} }

View File

@ -8,9 +8,10 @@
#pragma once #pragma once
#include "mutex.hpp"
#include "condition_variable.hpp"
#include <atomic> #include <atomic>
#include <mutex>
#include <condition_variable>
namespace cruft::thread { namespace cruft::thread {
/// a fire-once event that users can wait upon. if already fired then /// a fire-once event that users can wait upon. if already fired then
@ -33,7 +34,7 @@ namespace cruft::thread {
private: private:
std::atomic<bool> fired; std::atomic<bool> fired;
std::mutex m_mutex; ::cruft::thread::mutex m_mutex;
std::condition_variable m_condition; ::cruft::thread::condition_variable m_condition;
}; };
} }

4
thread/fwd.hpp Normal file
View File

@ -0,0 +1,4 @@
#pragma once
namespace cruft::thread {
}

View File

@ -9,18 +9,18 @@
#ifndef CRUFT_UTIL_THREAD_MONITOR_HPP #ifndef CRUFT_UTIL_THREAD_MONITOR_HPP
#define CRUFT_UTIL_THREAD_MONITOR_HPP #define CRUFT_UTIL_THREAD_MONITOR_HPP
#include <mutex> #include "mutex.hpp"
#include <utility> #include <utility>
#include <functional> #include <functional>
namespace cruft::thread { namespace cruft::thread {
template <typename ValueT, typename MutexT = std::mutex> template <typename ValueT, typename MutexT = cruft::thread::mutex>
class monitor { class monitor {
public: public:
template <typename ...Args> template <typename ...Args>
monitor (Args &&...args): monitor (Args &&...args):
m_value (std::forward<Args> (args)...) m_value (::std::forward<Args> (args)...)
{ ; } { ; }
class proxy { class proxy {
@ -36,7 +36,7 @@ namespace cruft::thread {
} }
private: private:
std::lock_guard<MutexT> m_guard; ::std::lock_guard<MutexT> m_guard;
ValueT &m_value; ValueT &m_value;
}; };

19
thread/mutex.hpp Normal file
View File

@ -0,0 +1,19 @@
/*
* 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 "../platform.hpp"
#if defined(PLATFORM_LINUX)
#include "mutex_std.hpp"
#elif defined(PLATFORM_WIN32)
#include "mutex_win32.hpp"
#else
#error "Unsupported mutex platform"
#endif

17
thread/mutex_std.hpp Normal file
View File

@ -0,0 +1,17 @@
/*
* 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 <mutex>
namespace cruft::thread {
inline namespace std {
using mutex = ::std::mutex;
}
}

39
thread/mutex_win32.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "mutex.hpp"
using cruft::thread::win32::mutex;
///////////////////////////////////////////////////////////////////////////////
mutex::~mutex ()
{
DeleteCriticalSection (&m_section);
}
///////////////////////////////////////////////////////////////////////////////
void mutex::lock (void)
{
EnterCriticalSection (&m_section);
}
//-----------------------------------------------------------------------------
void mutex::unlock (void)
{
LeaveCriticalSection (&m_section);
}
//-----------------------------------------------------------------------------
bool mutex::try_lock (void)
{
return TryEnterCriticalSection (&m_section);
}
///////////////////////////////////////////////////////////////////////////////
CRITICAL_SECTION&
mutex::native_handle (void)
{
return m_section;
}

41
thread/mutex_win32.hpp Normal file
View File

@ -0,0 +1,41 @@
/*
* 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 "../win32/handle.hpp"
#include "../win32/windows.hpp"
// Include the std mutex header so we can get access to expected
// functionality like std::lock_guard.
#include <mutex>
namespace cruft::thread {
inline namespace win32 {
class mutex {
public:
constexpr mutex () noexcept
: m_section {}
{
InitializeCriticalSection (&m_section);
}
mutex (mutex const&) = delete;
~mutex ();
void lock (void);
void unlock (void);
bool try_lock (void);
CRITICAL_SECTION& native_handle (void);
private:
CRITICAL_SECTION m_section;
};
};
}

View File

@ -6,9 +6,6 @@
* Copyright 2018 Danny Robson <danny@nerdcruft.net> * Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/ */
#ifndef CRUFT_UTIL_THREAD_SEMAPHORE_HPP
#define CRUFT_UTIL_THREAD_SEMAPHORE_HPP
#include <atomic> #include <atomic>
namespace cruft::thread { namespace cruft::thread {
@ -37,8 +34,6 @@ namespace cruft::thread {
int operator-- (void); int operator-- (void);
private: private:
std::atomic<int> m_value; ::std::atomic<int> m_value;
}; };
}; };
#endif

View File

@ -42,6 +42,6 @@ namespace cruft::thread {
private: private:
std::atomic<LONG> m_value; std::atomic<LONG> m_value;
win32::handle m_handle; ::cruft::win32::handle m_handle;
}; };
}; };

View File

@ -37,10 +37,10 @@ namespace cruft::thread {
private: private:
static_assert ( static_assert (
std::atomic<bool>::is_always_lock_free, ::std::atomic<bool>::is_always_lock_free,
"we require a lock free primitive or the entire point is moot" "we require a lock free primitive or the entire point is moot"
); );
std::atomic<bool> held; ::std::atomic<bool> held;
}; };
}; };

19
thread/thread.hpp Normal file
View File

@ -0,0 +1,19 @@
/*
* 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 "../platform.hpp"
#if defined(PLATFORM_LINUX)
#include "thread_std.hpp"
#elif defined(PLATFORM_WIN32)
#include "thread_win32.hpp"
#else
#error "Unsupported threading system"
#endif

18
thread/thread_std.hpp Normal file
View File

@ -0,0 +1,18 @@
/*
* 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>
namespace cruft::thread {
inline namespace std {
using thread = ::std::thread;
namespace this_thread = ::std::this_thread;
}
}

47
thread/thread_win32.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "thread_win32.hpp"
using cruft::thread::win32::thread;
///////////////////////////////////////////////////////////////////////////////
thread::thread () noexcept
{ ; }
//-----------------------------------------------------------------------------
thread::thread (thread &&rhs) noexcept
{
std::swap (m_handle, rhs.m_handle);
}
//-----------------------------------------------------------------------------
thread&
thread::operator= (thread &&rhs) noexcept
{
std::swap (m_handle, rhs.m_handle);
return *this;
}
///////////////////////////////////////////////////////////////////////////////
void thread::join ()
{
WaitForSingleObject (m_handle, INFINITE);
}
///////////////////////////////////////////////////////////////////////////////
unsigned int thread::hardware_concurrency (void) noexcept
{
SYSTEM_INFO res;
GetNativeSystemInfo (&res);
return res.dwNumberOfProcessors;
}
///////////////////////////////////////////////////////////////////////////////
void cruft::thread::win32::this_thread::yield (void) noexcept
{
Sleep (0);
}

144
thread/thread_win32.hpp Normal file
View File

@ -0,0 +1,144 @@
/*
* 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 "win32/handle.hpp"
#include "win32/windows.hpp"
#include "win32/except.hpp"
#include <chrono>
namespace cruft::thread {
inline namespace win32 {
namespace detail {
template <class T>
std::decay_t<T>
decay_copy (T&& v)
{
return std::forward<T>(v);
}
template <typename FunctionT, typename ...ArgsT>
class dispatcher {
public:
dispatcher (FunctionT &&func, ArgsT &&...args)
: m_func (std::forward<FunctionT> (func))
, m_args (std::forward<ArgsT> (args)...)
{ ; }
void run (void)
{
return _run (
std::make_index_sequence<sizeof...(ArgsT)> {}
);
}
private:
template <std::size_t... S>
void _run (std::index_sequence <S...>)
{
std::invoke (
decay_copy (std::forward<FunctionT> (m_func)),
decay_copy (std::forward<ArgsT> (std::get<S> (m_args)))...
);
}
FunctionT m_func;
std::tuple<ArgsT...> m_args;
};
template <typename DispatcherT>
DWORD WINAPI run (LPVOID arg)
{
auto *obj = reinterpret_cast<DispatcherT*> (arg);
try {
obj->run ();
return 0;
} catch (...) {
delete obj;
throw;
}
}
};
struct thread {
using id = ::cruft::win32::handle::native_type;
thread () noexcept;
thread (thread &&) noexcept;
template <typename FunctionT, typename... ArgsT>
thread (FunctionT &&func, ArgsT&&...args)
{
using dispatch_t = detail::dispatcher<FunctionT,ArgsT...>;
auto data = std::make_unique<dispatch_t> (
std::forward<FunctionT> (func),
std::forward<ArgsT> (args)...
);
m_handle.reset (
CreateThread (
nullptr, 0,
detail::run<dispatch_t>,
static_cast<void*> (data.get ()),
0,
nullptr
)
);
if (!m_handle)
throw std::system_error (
cruft::win32::error::last_code (),
std::generic_category ()
);
data.release ();
}
thread (thread const&) = delete;
thread& operator= (thread&&) noexcept;
void join (void);
static unsigned int hardware_concurrency () noexcept;
private:
::cruft::win32::handle m_handle;
};
namespace this_thread {
void yield (void) noexcept;
thread::id get_id (void) noexcept;
template <class Rep, class Period>
void sleep_for (
std::chrono::duration<Rep, Period> const& sleep_duration
) {
auto remain = std::chrono::duration_cast<std::chrono::milliseconds> (
sleep_duration
).count ();
while (remain > 0) {
auto const step = cruft::min (remain, INFINITE - 1);
Sleep (remain);
remain -= step;
}
}
template <class Clock, class Duration>
void sleep_until (std::chrono::time_point<Clock,Duration> const& sleep_time);
}
};
}

View File

@ -6,9 +6,6 @@
* Copyright 2018 Danny Robson <danny@nerdcruft.net> * Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/ */
#ifndef CRUFT_UTIL_THREAD_TICKETLOCK_HPP
#define CRUFT_UTIL_THREAD_TICKETLOCK_HPP
#include <atomic> #include <atomic>
namespace cruft::thread { namespace cruft::thread {
@ -20,9 +17,7 @@ namespace cruft::thread {
void unlock (void); void unlock (void);
private: private:
std::atomic<unsigned> current; ::std::atomic<unsigned> current;
std::atomic<unsigned> next; ::std::atomic<unsigned> next;
}; };
}; };
#endif

View File

@ -8,12 +8,13 @@
#include "time.hpp" #include "time.hpp"
#include <thread> #include "thread/thread.hpp"
#include <chrono> #include <chrono>
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
void void
cruft::sleep (uint64_t ns) cruft::sleep (uint64_t ns)
{ {
std::this_thread::sleep_for (std::chrono::nanoseconds (ns)); cruft::thread::this_thread::sleep_for (std::chrono::nanoseconds (ns));
} }

View File

@ -14,6 +14,8 @@
namespace cruft::win32 { namespace cruft::win32 {
struct handle { struct handle {
using native_type = HANDLE;
handle (); handle ();
explicit handle (posix::fd&&); explicit handle (posix::fd&&);
explicit handle (HANDLE&&); explicit handle (HANDLE&&);