This commit is contained in:
Jordan Woyak 2025-12-15 17:14:35 -06:00 committed by GitHub
commit f72921af76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 46 additions and 36 deletions

View File

@ -45,7 +45,13 @@ private:
struct Func : FuncBase struct Func : FuncBase
{ {
explicit Func(F&& f) : func{std::forward<F>(f)} {} explicit Func(F&& f) : func{std::forward<F>(f)} {}
result_type Invoke(Args... args) override { return func(std::forward<Args>(args)...); } result_type Invoke(Args... args) override
{
if constexpr (std::is_void_v<result_type>)
func(std::forward<Args>(args)...);
else
return func(std::forward<Args>(args)...);
}
std::decay_t<F> func; std::decay_t<F> func;
}; };

View File

@ -48,4 +48,18 @@ using AtomicMutex = detail::AtomicMutexBase<true>;
// Very fast to lock and unlock when uncontested (~3x faster than std::mutex). // Very fast to lock and unlock when uncontested (~3x faster than std::mutex).
using SpinMutex = detail::AtomicMutexBase<false>; using SpinMutex = detail::AtomicMutexBase<false>;
// This "mutex" class provides no actual thread synchronization.
// It has a std::shared_mutex interface only to be compatible with standard locks.
// Useful when template parameters can determine that a mutex is not actually needed.
struct DummyMutex
{
constexpr void lock() {}
constexpr bool try_lock() { return true; }
constexpr void unlock() {}
constexpr void lock_shared() {}
constexpr bool try_lock_shared() { return true; }
constexpr void unlock_shared() {}
};
} // namespace Common } // namespace Common

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <concepts>
#include <functional> #include <functional>
#include <future> #include <future>
#include <mutex> #include <mutex>
@ -10,6 +11,8 @@
#include <thread> #include <thread>
#include "Common/Event.h" #include "Common/Event.h"
#include "Common/Functional.h"
#include "Common/Mutex.h"
#include "Common/SPSCQueue.h" #include "Common/SPSCQueue.h"
#include "Common/Thread.h" #include "Common/Thread.h"
@ -17,12 +20,10 @@ namespace Common
{ {
namespace detail namespace detail
{ {
template <typename T, bool IsSingleProducer> template <typename T, typename FunctionType, bool IsSingleProducer>
class WorkQueueThreadBase final class WorkQueueThreadBase final
{ {
public: public:
using FunctionType = std::function<void(T)>;
WorkQueueThreadBase() = default; WorkQueueThreadBase() = default;
WorkQueueThreadBase(std::string name, FunctionType function) WorkQueueThreadBase(std::string name, FunctionType function)
{ {
@ -99,7 +100,7 @@ public:
} }
private: private:
using CommandFunction = std::function<void()>; using CommandFunction = MoveOnlyFunction<void()>;
// Blocking. // Blocking.
void RunCommand(CommandFunction cmd) void RunCommand(CommandFunction cmd)
@ -128,25 +129,13 @@ private:
m_commands.Clear(); m_commands.Clear();
} }
auto GetLockGuard() auto GetLockGuard() { return std::lock_guard{m_mutex}; }
{
struct DummyLockGuard
{
// Silences unused variable warning.
~DummyLockGuard() { void(); }
};
if constexpr (IsSingleProducer)
return DummyLockGuard{};
else
return std::lock_guard{m_mutex};
}
bool IsRunning() { return m_thread.joinable(); } bool IsRunning() { return m_thread.joinable(); }
void ThreadLoop(const std::string& thread_name, const FunctionType& function) void ThreadLoop(const std::string& thread_name, const FunctionType& function)
{ {
Common::SetCurrentThreadName(thread_name.c_str()); SetCurrentThreadName(thread_name.c_str());
while (true) while (true)
{ {
@ -173,35 +162,32 @@ private:
} }
std::thread m_thread; std::thread m_thread;
Common::WaitableSPSCQueue<T> m_items; WaitableSPSCQueue<T> m_items;
Common::WaitableSPSCQueue<CommandFunction> m_commands; WaitableSPSCQueue<CommandFunction> m_commands;
Common::Event m_event; Event m_event;
using DummyMutex = std::type_identity<void>;
using ProducerMutex = std::conditional_t<IsSingleProducer, DummyMutex, std::recursive_mutex>; using ProducerMutex = std::conditional_t<IsSingleProducer, DummyMutex, std::recursive_mutex>;
ProducerMutex m_mutex; ProducerMutex m_mutex;
}; };
// A WorkQueueThread-like class that takes functions to invoke. // A WorkQueueThread-like class that takes functions to invoke.
template <template <typename> typename WorkThread> template <template <typename, typename> typename WorkThread>
class AsyncWorkThreadBase class AsyncWorkThreadBase
{ {
public: public:
using FuncType = std::function<void()>; using FuncType = MoveOnlyFunction<void()>;
AsyncWorkThreadBase() = default; AsyncWorkThreadBase() = default;
explicit AsyncWorkThreadBase(std::string thread_name) { Reset(std::move(thread_name)); } explicit AsyncWorkThreadBase(std::string thread_name) { Reset(std::move(thread_name)); }
void Reset(std::string thread_name) void Reset(std::string thread_name) { m_worker.Reset(std::move(thread_name), {}); }
{
m_worker.Reset(std::move(thread_name), std::invoke<FuncType>);
}
void Push(FuncType func) { m_worker.Push(std::move(func)); } void Push(FuncType func) { m_worker.Push(std::move(func)); }
auto PushBlocking(FuncType func) template <std::invocable<> Func>
auto PushBlocking(Func&& func)
{ {
std::packaged_task task{std::move(func)}; std::packaged_task task{std::forward<Func>(func)};
m_worker.EmplaceItem([&] { task(); }); m_worker.EmplaceItem([&] { task(); });
return task.get_future().get(); return task.get_future().get();
} }
@ -211,18 +197,22 @@ public:
void WaitForCompletion() { m_worker.WaitForCompletion(); } void WaitForCompletion() { m_worker.WaitForCompletion(); }
private: private:
WorkThread<FuncType> m_worker; // Must get a pointer first to work around a NTTP struct MSVC bug.
// InvokerOf<&std::invoke<FuncType>> gives a nonsensical compiler error.
// Feel free to remove this in the future when it begins to compile.
static constexpr auto INVOKE_PTR = &std::invoke<FuncType>;
WorkThread<FuncType, InvokerOf<INVOKE_PTR>> m_worker;
}; };
} // namespace detail } // namespace detail
// Multiple threads may use the public interface. // Multiple threads may use the public interface.
template <typename T> template <typename T, typename FuncType = MoveOnlyFunction<void(T)>>
using WorkQueueThread = detail::WorkQueueThreadBase<T, false>; using WorkQueueThread = detail::WorkQueueThreadBase<T, FuncType, false>;
// A "Single Producer" WorkQueueThread. // A "Single Producer" WorkQueueThread.
// It uses no mutex but only one thread can safely manipulate the queue. // It uses no mutex but only one thread can safely manipulate the queue.
template <typename T> template <typename T, typename FuncType = MoveOnlyFunction<void(T)>>
using WorkQueueThreadSP = detail::WorkQueueThreadBase<T, true>; using WorkQueueThreadSP = detail::WorkQueueThreadBase<T, FuncType, true>;
using AsyncWorkThread = detail::AsyncWorkThreadBase<WorkQueueThread>; using AsyncWorkThread = detail::AsyncWorkThreadBase<WorkQueueThread>;
using AsyncWorkThreadSP = detail::AsyncWorkThreadBase<WorkQueueThreadSP>; using AsyncWorkThreadSP = detail::AsyncWorkThreadBase<WorkQueueThreadSP>;