Threading: Add SharedMutex lightweight wrapper

wip2
Stenzek 8 hours ago
parent 473025995f
commit 2e80d3e438
No known key found for this signature in database

@ -7,12 +7,15 @@
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <shared_mutex>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
static_assert(!std::is_copy_constructible_v<Threading::Mutex>); static_assert(!std::is_copy_constructible_v<Threading::Mutex>);
static_assert(!std::is_copy_assignable_v<Threading::Mutex>); static_assert(!std::is_copy_assignable_v<Threading::Mutex>);
static_assert(!std::is_copy_constructible_v<Threading::SharedMutex>);
static_assert(!std::is_copy_assignable_v<Threading::SharedMutex>);
static_assert(!std::is_copy_constructible_v<Threading::ConditionVariable>); static_assert(!std::is_copy_constructible_v<Threading::ConditionVariable>);
static_assert(!std::is_copy_assignable_v<Threading::ConditionVariable>); static_assert(!std::is_copy_assignable_v<Threading::ConditionVariable>);
@ -74,6 +77,83 @@ TEST(ThreadingMutex, MutualExclusion)
EXPECT_EQ(value, NUM_THREADS * NUM_INCREMENTS); EXPECT_EQ(value, NUM_THREADS * NUM_INCREMENTS);
} }
TEST(ThreadingSharedMutex, TryLock)
{
Threading::SharedMutex mutex;
EXPECT_TRUE(mutex.try_lock());
bool exclusive_locked = true;
bool shared_locked = true;
std::thread exclusive_test_thread([&]() {
exclusive_locked = mutex.try_lock();
shared_locked = mutex.try_lock_shared();
});
exclusive_test_thread.join();
EXPECT_FALSE(exclusive_locked);
EXPECT_FALSE(shared_locked);
mutex.unlock();
EXPECT_TRUE(mutex.try_lock_shared());
std::thread shared_test_thread([&]() {
shared_locked = mutex.try_lock_shared();
if (shared_locked)
mutex.unlock_shared();
exclusive_locked = mutex.try_lock();
});
shared_test_thread.join();
EXPECT_TRUE(shared_locked);
EXPECT_FALSE(exclusive_locked);
mutex.unlock_shared();
EXPECT_TRUE(mutex.try_lock());
mutex.unlock();
}
TEST(ThreadingSharedMutex, StandardLockWrappers)
{
Threading::SharedMutex mutex;
{
const std::lock_guard lock(mutex);
}
{
std::unique_lock lock(mutex);
EXPECT_TRUE(lock.owns_lock());
}
{
std::shared_lock lock(mutex);
EXPECT_TRUE(lock.owns_lock());
}
}
TEST(ThreadingSharedMutex, MutualExclusion)
{
static constexpr u32 NUM_THREADS = 4;
static constexpr u32 NUM_INCREMENTS = 10000;
Threading::SharedMutex mutex;
u32 value = 0;
std::vector<std::thread> threads;
threads.reserve(NUM_THREADS);
for (u32 i = 0; i < NUM_THREADS; i++)
{
threads.emplace_back([&mutex, &value]() {
for (u32 j = 0; j < NUM_INCREMENTS; j++)
{
const std::lock_guard lock(mutex);
value++;
}
});
}
for (std::thread& thread : threads)
thread.join();
EXPECT_EQ(value, NUM_THREADS * NUM_INCREMENTS);
}
TEST(ThreadingConditionVariable, NotifyOneAndPredicateWait) TEST(ThreadingConditionVariable, NotifyOneAndPredicateWait)
{ {
Threading::Mutex mutex; Threading::Mutex mutex;

@ -55,9 +55,11 @@ LOG_CHANNEL(Threading);
#ifdef _WIN32 #ifdef _WIN32
#define NATIVE_MUTEX_PTR(storage) static_cast<SRWLOCK*>(static_cast<void*>(&(storage))) #define NATIVE_MUTEX_PTR(storage) static_cast<SRWLOCK*>(static_cast<void*>(&(storage)))
#define NATIVE_SHARED_MUTEX_PTR(storage) static_cast<SRWLOCK*>(static_cast<void*>(&(storage)))
#define NATIVE_CONDITION_VARIABLE_PTR(storage) static_cast<CONDITION_VARIABLE*>(static_cast<void*>(&(storage))) #define NATIVE_CONDITION_VARIABLE_PTR(storage) static_cast<CONDITION_VARIABLE*>(static_cast<void*>(&(storage)))
#else #else
#define NATIVE_MUTEX_PTR(storage) static_cast<pthread_mutex_t*>(static_cast<void*>(&(storage))) #define NATIVE_MUTEX_PTR(storage) static_cast<pthread_mutex_t*>(static_cast<void*>(&(storage)))
#define NATIVE_SHARED_MUTEX_PTR(storage) static_cast<pthread_rwlock_t*>(static_cast<void*>(&(storage)))
#define NATIVE_CONDITION_VARIABLE_PTR(storage) static_cast<pthread_cond_t*>(static_cast<void*>(&(storage))) #define NATIVE_CONDITION_VARIABLE_PTR(storage) static_cast<pthread_cond_t*>(static_cast<void*>(&(storage)))
#endif #endif
@ -674,6 +676,86 @@ void Threading::Mutex::unlock()
#ifndef _WIN32 #ifndef _WIN32
Threading::SharedMutex::SharedMutex()
{
static_assert(sizeof(m_data) == sizeof(pthread_rwlock_t));
static_assert(alignof(SharedMutex) >= alignof(pthread_rwlock_t));
[[maybe_unused]] const int result = pthread_rwlock_init(NATIVE_SHARED_MUTEX_PTR(m_data), nullptr);
DebugAssert(result == 0);
}
Threading::SharedMutex::~SharedMutex()
{
[[maybe_unused]] const int result = pthread_rwlock_destroy(NATIVE_SHARED_MUTEX_PTR(m_data));
DebugAssert(result == 0);
}
#endif // _WIN32
void Threading::SharedMutex::lock()
{
#ifdef _WIN32
AcquireSRWLockExclusive(NATIVE_SHARED_MUTEX_PTR(m_data));
#else
[[maybe_unused]] const int result = pthread_rwlock_wrlock(NATIVE_SHARED_MUTEX_PTR(m_data));
DebugAssert(result == 0);
#endif
}
bool Threading::SharedMutex::try_lock()
{
#ifdef _WIN32
return TryAcquireSRWLockExclusive(NATIVE_SHARED_MUTEX_PTR(m_data)) != FALSE;
#else
const int result = pthread_rwlock_trywrlock(NATIVE_SHARED_MUTEX_PTR(m_data));
DebugAssert(result == 0 || result == EBUSY);
return result == 0;
#endif
}
void Threading::SharedMutex::unlock()
{
#ifdef _WIN32
ReleaseSRWLockExclusive(NATIVE_SHARED_MUTEX_PTR(m_data));
#else
[[maybe_unused]] const int result = pthread_rwlock_unlock(NATIVE_SHARED_MUTEX_PTR(m_data));
DebugAssert(result == 0);
#endif
}
void Threading::SharedMutex::lock_shared()
{
#ifdef _WIN32
AcquireSRWLockShared(NATIVE_SHARED_MUTEX_PTR(m_data));
#else
[[maybe_unused]] const int result = pthread_rwlock_rdlock(NATIVE_SHARED_MUTEX_PTR(m_data));
DebugAssert(result == 0);
#endif
}
bool Threading::SharedMutex::try_lock_shared()
{
#ifdef _WIN32
return TryAcquireSRWLockShared(NATIVE_SHARED_MUTEX_PTR(m_data)) != FALSE;
#else
const int result = pthread_rwlock_tryrdlock(NATIVE_SHARED_MUTEX_PTR(m_data));
DebugAssert(result == 0 || result == EBUSY || result == EAGAIN);
return result == 0;
#endif
}
void Threading::SharedMutex::unlock_shared()
{
#ifdef _WIN32
ReleaseSRWLockShared(NATIVE_SHARED_MUTEX_PTR(m_data));
#else
[[maybe_unused]] const int result = pthread_rwlock_unlock(NATIVE_SHARED_MUTEX_PTR(m_data));
DebugAssert(result == 0);
#endif
}
#ifndef _WIN32
Threading::ConditionVariable::ConditionVariable() Threading::ConditionVariable::ConditionVariable()
{ {
static_assert(sizeof(m_data) == sizeof(pthread_cond_t)); static_assert(sizeof(m_data) == sizeof(pthread_cond_t));
@ -937,6 +1019,7 @@ bool Threading::KernelSemaphore::TryWait()
} }
#undef NATIVE_MUTEX_PTR #undef NATIVE_MUTEX_PTR
#undef NATIVE_SHARED_MUTEX_PTR
#undef NATIVE_CONDITION_VARIABLE_PTR #undef NATIVE_CONDITION_VARIABLE_PTR
#if !defined(_WIN32) #if !defined(_WIN32)
#undef NATIVE_SEMAPHORE #undef NATIVE_SEMAPHORE

@ -165,6 +165,51 @@ private:
#endif #endif
}; };
// --------------------------------------------------------------------------------------
// SharedMutex
// --------------------------------------------------------------------------------------
// A lightweight replacement for std::shared_mutex. The native object is stored inline
// without exposing platform headers to users of this header.
//
class SharedMutex
{
public:
#ifdef _WIN32
SharedMutex() = default;
#else
SharedMutex();
~SharedMutex();
#endif
SharedMutex(const SharedMutex&) = delete;
SharedMutex& operator=(const SharedMutex&) = delete;
void lock();
bool try_lock();
void unlock();
void lock_shared();
bool try_lock_shared();
void unlock_shared();
private:
#if defined(_WIN32)
void* m_data = nullptr;
#elif defined(__APPLE__)
static constexpr u32 NATIVE_STORAGE_SIZE = 200;
#elif defined(__ANDROID__)
static constexpr u32 NATIVE_STORAGE_SIZE = (sizeof(void*) == 8) ? 56 : 40;
#elif defined(__linux__)
static constexpr u32 NATIVE_STORAGE_SIZE = (sizeof(void*) == 8) ? 56 : 32;
#else
#error Unsupported platform.
#endif
#if !defined(_WIN32)
alignas(void*) u8 m_data[NATIVE_STORAGE_SIZE];
#endif
};
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// ConditionVariable // ConditionVariable
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------

Loading…
Cancel
Save