How Would You Implement Your Own Reader/Writer Lock in C++11

How can I implement a C++ Reader-Writer lock using a single unlock method, which can be called be a reader or writer?

Well, define two functions UnlockRead and UnlockWrite.

I believe you do not need both accesses (Write/Read) at the same time in the same place. So what I am proposing is to have two other classes for locking 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 read and write lock and use RAII to be always on safe side:

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, the very similar implementation you can easily write by yourself for WriteLock


Since OP insisted in comments to have "one" Unlock - please consider the drawbacks:

Assume it is implemented some kind of stack of last calls to Lock functions:

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 would work only in one-thread application. And one-thread application never need any locks.

So - you have to have multi-thread stack - like:

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 use this MultiThreadStack not std::stack in ReadWriteLock.

But, the std::map above would need ReadWriteLock to lock access to it from multuple threads - so, well, either you know all your threads before you start using this stuff (preregistration) or you end up in the same problem as described here. So my advice - if you can - change your design.

A RW lock for c++11 threads

std::shared_mutex will be part of the C++14 Standard Library. It did not make it to C++11 just because there was no time to formulate a proposal and discuss it thoroughly.

You can still use boost::shared_mutex though. Under Windows, if you are working with Windows Vista or later, you can use Slim Read-Write Locks, which are optimized for speed and memory consumption.

A readers/writer lock... without having a lock for the readers?

What you're describing is very similar to double instance locking and left-right concurrency control.

In terms of progress guarantees, the difference between the two is that the former is lock-free for readers while the latter is wait-free. Both are blocking for writers.

How to implement a reader/writer lock in C++14

C++14 has the read/writer lock implementation std::shared_timed_mutex.

Side-note: C++17 added the simpler class std::shared_mutex, which you can use if you don't need the extra timing functions (like shared_timed_mutex::try_lock_for and shared_timed_mutex::try_lock_until).

However, before using a read/writer lock, be aware of the potentially harmful performance implications. Depending on the situation, a simple std::mutex might be faster.

optimize the implementation of read/write locks


  1. whether I should use notify_all when releasing the lock? According to the document, once a thread gets notified, it will reacquire the lock. If using notify_all, then multiple threads can reacquire the same lock. What will happen then? And whether will a thread acquire the lock before checking the condition (i.e., lock_!=NO_LOCK && lock_!=READ_LOCK)?

Yes, you should use notify_all when releasing the lock. All condition_ waiting for the mutex_ will be waked one by one for they must lock the mutex_ firstly(this is done inner the condition_ wait operation).


  1. how can I optimize the program? obviously, when releasing a read lock, we only need to notify the threads that attempt to acquire the write lock, since read never blocks read. So how to implement this idea?

All threads waiting for mutex_ must be notified, for some writing threads may waiting for the read lock to be released.

I hope this will help you!

Reader/Writer Locks in C++

Newer versions of boost::thread have read/write locks (1.35.0 and later, apparently the previous versions did not work correctly).

They have the names shared_lock, unique_lock, and upgrade_lock and operate on a shared_mutex.



Related Topics



Leave a reply



Submit