How can I implement a C ++ read-write lock using a single unlock method that can be called a reader or a writer?

I am working on a project that requires the use of specific OS abstractions and I need to implement read / write locking using their semaphore and mutex. I currently have a setup in the format:

class ReadWriteLock
{
public:
   ReadWriteLock(uint32_t maxReaders);
   ~ReadWriteLock();
   uint32_t GetMaxReaders() const;
   eResult  GetReadLock(int32_t timeout);
   eResult  GetWriteLock(int32_t timeout);
   eResult  Unlock();

private:
   uint32_t m_MaxReaders;
   Mutex* m_WriterMutex;
   Semaphore* m_ReaderSemaphore;

};

      

In this implementation, I need to use this unlocking method to unlock the write and release all the reader semaphore slots, or just unbind the reader semaphore slot, however I am afraid as I cannot imagine an implementation that would work in all cases. How can I make this work in a given setup? I know this is possible as POSIX was able to implement a generic unlock method in its implementation, but I can't find any indication of how this was done, so appreciate any information people can share.

Please note that I cannot use C ++ 11 or other OS primitives.

+3


source to share


2 answers


Well, define two functions UnlockRead

and UnlockWrite

.

I believe you don't need both (Write / Read) access at the same time in the same place. Therefore, I suggest having two other classes for blocking access:

class ReadWriteAccess
{
public:
   ReadWriteAccess(uint32_t maxReaders);
   ~ReadWriteAccess();
   uint32_t GetMaxReaders() const;
   uint32_t GetMaxReaders() const;
   eResult  GetReadLock(int32_t timeout);
   eResult  GetWriteLock(int32_t timeout);
   eResult  UnlockWrite();
   eResult  UnlockRead();

private:
   uint32_t m_MaxReaders;
   Mutex* m_WriterMutex;
   Semaphore* m_ReaderSemaphore;

};

      

And have separate classes for blocking read and write and use RAII to be always safe:

class ReadLock
{
public:
    ReadLock(ReadWriteAccess& access, int32_t timeout) : access(access) 
    {
        result = access.GetReadLock(timeout);
    }
    eResult getResult() const { return result; }
    ~ReadLock()
    {
        if (result)
            access.UnlockRead();
    }
private:
    ReadWriteAccess& access;
    eResult  result;
};

      

and use like this:

T someResource;
ReadWriteAccess someResourceGuard;

void someFunction()
{
    ReadLock lock(someResourceGuard);
    if (lock.getResult())
       cout << someResource; // it is safe to read something from resource
}

      

Of course, a very similar implementation that you can easily write yourself for WriteLock




As the OP insisted on comments to have a "one" unlock - consider the downsides:

Let's assume that some stack of recent calls to Lock functions is implemented:

class ReadWriteLock
{
public:
   ReadWriteLock(uint32_t maxReaders);
   ~ReadWriteLock();
   uint32_t GetMaxReaders() const;
   eResult  GetReadLock(int32_t timeout)
   {
       eResult result = GetReadLockImpl(timestamp);
       if (result)
           lockStack.push(READ);
   }
   eResult  GetWriteLock(int32_t timeout)
   {
       eResult result = GetWriteLockImpl(timestamp);
       if (result)
           lockStack.push(WRITE);
   }
   eResult  Unlock()
   {
       LastLockMode lockMode = lockStack.top();
       lockStack.pop();
       if (lockMode == READ) 
           UnlockReadImpl();
       else
           UnlockWriteImpl();
   }

private:
   uint32_t m_MaxReaders;
   Mutex* m_WriterMutex;
   Semaphore* m_ReaderSemaphore;

    enum Mode { READ, WRITE };
    std::stack<Mode> lockStack;
};

      

But the above will only work in a single threaded application. And a single threaded application never needs any locks.

So - you must have a multithreaded stack - for example:

template <typename Value>
class MultiThreadStack
{
public:
    void push(Value)
    {
       stackPerThread[getThreadId()].push(value);
    }
    Value top()
    {
       return stackPerThread[getThreadId()].top();
    }
    void pop()
    {
       stackPerThread[getThreadId()].pop();
    }
private:
    ThreadId getThreadId() { return /* your system way to get thread id*/; }
    std::map<ThreadId, std::stack<Value>> stackPerThread;
};

      

So MultiThreadStack

don't use std :: stack in ReadWriteLock

.

But for the std::map

above std::map

need to ReadWriteLock

block access to it from multithreaded streams - so if you know all of your streams before starting to use this material (pre-registration) or you fall into the same problem that is described here . So my advice is - if you can - change your design.

+2


source


When a lock is successfully acquired, the type is known: either you have many readers or one writer, you cannot have both readers and writers working with a reliably acquired lock.



Thus, it is sufficient to keep the current blocking mode on a successful call lock

and all subsequent calls unlock

(potentially a lot if read permission was granted, only one is valid if a write-and-write was requested) will be in this mode.

+1


source







All Articles