First
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//---------------------------------------------------------
|
||||
// For conditions of distribution and use, see
|
||||
// https://github.com/preshing/cpp11-on-multicore/blob/master/LICENSE
|
||||
//---------------------------------------------------------
|
||||
|
||||
#ifndef EATHREAD_EATHREAD_RWSEMALOCK_H
|
||||
#define EATHREAD_EATHREAD_RWSEMALOCK_H
|
||||
|
||||
#include "eathread_atomic.h"
|
||||
#include "eathread_semaphore.h"
|
||||
|
||||
#if defined(EA_PRAGMA_ONCE_SUPPORTED)
|
||||
#pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result.
|
||||
#endif
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace Thread
|
||||
{
|
||||
//---------------------------------------------------------
|
||||
// RWSemaLock
|
||||
//---------------------------------------------------------
|
||||
class RWSemaLock
|
||||
{
|
||||
public:
|
||||
RWSemaLock() : mStatus(0) {}
|
||||
RWSemaLock(const RWSemaLock&) = delete;
|
||||
RWSemaLock(RWSemaLock&&) = delete;
|
||||
RWSemaLock& operator=(const RWSemaLock&) = delete;
|
||||
RWSemaLock& operator=(RWSemaLock&&) = delete;
|
||||
|
||||
void ReadLock()
|
||||
{
|
||||
Status oldStatus, newStatus;
|
||||
do
|
||||
{
|
||||
oldStatus.data = mStatus.GetValue();
|
||||
newStatus.data = oldStatus.data;
|
||||
|
||||
if (oldStatus.writers > 0)
|
||||
{
|
||||
newStatus.waitToRead++;
|
||||
}
|
||||
else
|
||||
{
|
||||
newStatus.readers++;
|
||||
}
|
||||
// CAS until successful. On failure, oldStatus will be updated with the latest value.
|
||||
}
|
||||
while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data));
|
||||
|
||||
if (oldStatus.writers > 0)
|
||||
{
|
||||
mReadSema.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadTryLock()
|
||||
{
|
||||
Status oldStatus, newStatus;
|
||||
do
|
||||
{
|
||||
oldStatus.data = mStatus.GetValue();
|
||||
newStatus.data = oldStatus.data;
|
||||
|
||||
if (oldStatus.writers > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
newStatus.readers++;
|
||||
}
|
||||
// CAS until successful. On failure, oldStatus will be updated with the latest value.
|
||||
}
|
||||
while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReadUnlock()
|
||||
{
|
||||
Status oldStatus;
|
||||
oldStatus.data = mStatus.Add(-Status::kIncrementRead) + Status::kIncrementRead;
|
||||
|
||||
EAT_ASSERT(oldStatus.readers > 0);
|
||||
if (oldStatus.readers == 1 && oldStatus.writers > 0)
|
||||
{
|
||||
mWriteSema.Post();
|
||||
}
|
||||
}
|
||||
|
||||
void WriteLock()
|
||||
{
|
||||
Status oldStatus;
|
||||
oldStatus.data = mStatus.Add(Status::kIncrementWrite) - Status::kIncrementWrite;
|
||||
EAT_ASSERT(oldStatus.writers + 1 <= Status::kMaximum);
|
||||
if (oldStatus.readers > 0 || oldStatus.writers > 0)
|
||||
{
|
||||
mWriteSema.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
bool WriteTryLock()
|
||||
{
|
||||
Status oldStatus, newStatus;
|
||||
do
|
||||
{
|
||||
oldStatus.data = mStatus.GetValue();
|
||||
newStatus.data = oldStatus.data;
|
||||
|
||||
if (oldStatus.writers > 0 || oldStatus.readers > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
newStatus.writers++;
|
||||
}
|
||||
// CAS until successful. On failure, oldStatus will be updated with the latest value.
|
||||
}
|
||||
while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteUnlock()
|
||||
{
|
||||
uint32_t waitToRead = 0;
|
||||
Status oldStatus, newStatus;
|
||||
do
|
||||
{
|
||||
oldStatus.data = mStatus.GetValue();
|
||||
EAT_ASSERT(oldStatus.readers == 0);
|
||||
newStatus.data = oldStatus.data;
|
||||
newStatus.writers--;
|
||||
waitToRead = oldStatus.waitToRead;
|
||||
if (waitToRead > 0)
|
||||
{
|
||||
newStatus.waitToRead = 0;
|
||||
newStatus.readers = waitToRead;
|
||||
}
|
||||
// CAS until successful. On failure, oldStatus will be updated with the latest value.
|
||||
}
|
||||
while (!mStatus.SetValueConditional(newStatus.data, oldStatus.data));
|
||||
|
||||
if (waitToRead > 0)
|
||||
{
|
||||
mReadSema.Post(waitToRead);
|
||||
}
|
||||
else if (oldStatus.writers > 1)
|
||||
{
|
||||
mWriteSema.Post();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(rparolin):
|
||||
// Since the RWSemaLock uses atomics to update its status flags before blocking on a semaphore, you cannot
|
||||
// rely on the answer the IsReadLocked/IsWriteLocked gives you. It's at a best a guess and you can't rely
|
||||
// on it for any kind of validation checks which limits its usefulness. In addition, the original
|
||||
// implementation from Preshing does not include such functionality.
|
||||
//
|
||||
// bool IsReadLocked() {...}
|
||||
// bool IsWriteLocked() {...}
|
||||
|
||||
protected:
|
||||
EA_DISABLE_VC_WARNING(4201) // warning C4201: nonstandard extension used: nameless struct/union
|
||||
union Status
|
||||
{
|
||||
enum
|
||||
{
|
||||
kIncrementRead = 1,
|
||||
kIncrementWaitToRead = 1 << 10,
|
||||
kIncrementWrite = 1 << 20,
|
||||
kMaximum = (1 << 10) - 1,
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
int readers : 10; // 10-bits = 1024
|
||||
int waitToRead : 10;
|
||||
int writers : 10;
|
||||
int pad : 2;
|
||||
};
|
||||
|
||||
int data;
|
||||
};
|
||||
EA_RESTORE_VC_WARNING()
|
||||
|
||||
AtomicInt32 mStatus;
|
||||
Semaphore mReadSema; // semaphores are non-copyable
|
||||
Semaphore mWriteSema; // semaphores are non-copyable
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------
|
||||
// ReadLockGuard
|
||||
//---------------------------------------------------------
|
||||
class AutoSemaReadLock
|
||||
{
|
||||
private:
|
||||
RWSemaLock& m_lock;
|
||||
|
||||
public:
|
||||
AutoSemaReadLock(const AutoSemaReadLock&) = delete;
|
||||
AutoSemaReadLock(AutoSemaReadLock&&) = delete;
|
||||
AutoSemaReadLock& operator=(const AutoSemaReadLock&) = delete;
|
||||
AutoSemaReadLock& operator=(AutoSemaReadLock&&) = delete;
|
||||
|
||||
AutoSemaReadLock(RWSemaLock& lock) : m_lock(lock)
|
||||
{
|
||||
m_lock.ReadLock();
|
||||
}
|
||||
|
||||
~AutoSemaReadLock()
|
||||
{
|
||||
m_lock.ReadUnlock();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------
|
||||
// WriteLockGuard
|
||||
//---------------------------------------------------------
|
||||
class AutoSemaWriteLock
|
||||
{
|
||||
private:
|
||||
RWSemaLock& m_lock;
|
||||
|
||||
public:
|
||||
AutoSemaWriteLock(const AutoSemaWriteLock&) = delete;
|
||||
AutoSemaWriteLock(AutoSemaWriteLock&&) = delete;
|
||||
AutoSemaWriteLock& operator=(const AutoSemaWriteLock&) = delete;
|
||||
AutoSemaWriteLock& operator=(AutoSemaWriteLock&&) = delete;
|
||||
|
||||
AutoSemaWriteLock(RWSemaLock& lock) : m_lock(lock)
|
||||
{
|
||||
m_lock.WriteLock();
|
||||
}
|
||||
|
||||
~AutoSemaWriteLock()
|
||||
{
|
||||
m_lock.WriteUnlock();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // EATHREAD_EATHREAD_RWSEMALOCK_H
|
||||
Reference in New Issue
Block a user