user1918858
user1918858

Reputation: 1208

Read Write lock implementation in C++

I am trying to use read/write lock in C++ using shared_mutex

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock >  WriteLock;
typedef boost::shared_lock< Lock >  ReadLock;

class Test {
    Lock lock;
    WriteLock writeLock;
    ReadLock readLock;

    Test() : writeLock(lock), readLock(lock) {}

    readFn1() {
        readLock.lock();
        /*
             Some Code
        */
        readLock.unlock();
    }

    readFn2() {
        readLock.lock();
        /*
             Some Code
        */
        readLock.unlock();
    }

    writeFn1() {
        writeLock.lock();
        /*
             Some Code
        */
        writeLock.unlock();
    }

    writeFn2() {
        writeLock.lock();
        /*
             Some Code
        */
        writeLock.unlock();
    }
}

The code seems to be working fine but I have a few conceptual questions.

Q1. I have seen the recommendations to use unique_lock and shared_lock on http://en.cppreference.com/w/cpp/thread/shared_mutex/lock, but I don't understand why because shared_mutex already supports lock and lock_shared methods?

Q2. Does this code have the potential to cause write starvation? If yes then how can I avoid the starvation?

Q3. Is there any other locking class I can try to implement read write lock?

Upvotes: 5

Views: 3561

Answers (3)

Craig Estey
Craig Estey

Reputation: 33631

Q1. I have seen the recommendations to use unique_lock and shared_lock on http://en.cppreference.com/w/cpp/thread/shared_mutex/lock, but I don't understand why because shared_mutex already supports lock and lock_shared methods?

Possibly because unique_lock has been around since c++11 but shared_lock is coming onboard with c++17. Also, [possibly] unique_lock can be more efficient. Here's the original rationale for shared_lock [by the creator] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html and I defer to that.

Q2. Does this code have the potential to cause write starvation? If yes then how can I avoid the starvation?

Yes, absolutely. If you do:

while (1)
    writeFn1();

You can end up with a time line of:

T1: writeLock.lock()
T2: writeLock.unlock()

T3: writeLock.lock()
T4: writeLock.unlock()

T5: writeLock.lock()
T6: writeLock.unlock()

...

The difference T2-T1 is arbitrary, based on amount of work being done. But, T3-T2 is near zero. This is the window for another thread to acquire the lock. Because the window is so small, it probably won't get it.

To solve this, the simplest method is to insert a small sleep (e.g. nanosleep) between T2 and T3. You could do this by adding it to the bottom of writeFn1.

Other methods can involve creating a queue for the lock. If a thread can't get the lock, it adds itself to a queue and the first thread on the queue gets the lock when the lock is released. In the linux kernel, this is implemented for a "queued spinlock"

Q3. Is there any other locking class I can try to implement read write lock?

While not a class, you could use pthread_mutex_lock and pthread_mutex_unlock. These implement recursive locks. You could add your own code to implement the equivalent of boost::scoped_lock. Your class can control the semantics.

Or, boost has its own locks.

Upvotes: 1

Christophe
Christophe

Reputation: 73540

Q1: use of a mutex wrapper

The recommendation to use a wrapper object instead of managing the mutex directly is to avoid unfortunate situation where your code is interrupted and the mutex is not released, leaving it locked forever.

This is the principle of RAII.

But this only works if your ReadLock or WriteLock are local to the function using it.

Example:

readFn1() {
    boost::unique_lock< Lock > rl(lock);  
    /*
         Some Code 
         ==> imagine exception is thrown
    */
    rl.unlock();   // this is never reached if exception thrown 
}  // fortunately local object are destroyed automatically in case 
   // an excpetion makes you leave the function prematurely      

In your code this won't work if one of the function is interupted, becaus your ReadLock WriteLock object is a member of Test and not local to the function setting the lock.

Q2: Write starvation

It is not fully clear how you will invoke the readers and the writers, but yes, there is a risk:

  • as long as readers are active, the writer is blocked by the unique_lock waiting for the mutex to be aquirable in exclusive mode.
  • however as long as the wrtier is waiting, new readers can obtain access to the shared lock, causing the unique_lock to be further delayed.

If you want to avoid starvation, you have to ensure that waiting writers do get the opportunity to set their unique_lock. For example att in your readers some code to check if a writer is waiting before setting the lock.

Q3 Other locking classes

Not quite sure what you're looking for, but I have the impression that condition_variable could be of interest for you. But the logic is a little bit different.

Maybe, you could also find a solution by thinking out of the box: perhaps there's a suitable lock-free data structure that could facilitate coexistance of readers and writers by changing slightly the approach ?

Upvotes: 3

Jellybaby
Jellybaby

Reputation: 996

The types for the locks are ok but instead of having them as member functions create then inside the member functions locktype lock(mymutex). That way they are released on destruction even in the case of an exception.

Upvotes: 2

Related Questions