Example for boost shared_mutex (multiple reads/one write)?

103,922

Solution 1

It looks like you would do something like this:

boost::shared_mutex _access;
void reader()
{
  // get shared access
  boost::shared_lock<boost::shared_mutex> lock(_access);

  // now we have shared access
}

void writer()
{
  // get upgradable access
  boost::upgrade_lock<boost::shared_mutex> lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
  // now we have exclusive access
}

Solution 2

1800 INFORMATION is more or less correct, but there are a few issues I wanted to correct.

boost::shared_mutex _access;
void reader()
{
  boost::shared_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access
}

void conditional_writer()
{
  boost::upgrade_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access

  if (something) {
    boost::upgrade_to_unique_lock< boost::shared_mutex > uniqueLock(lock);
    // do work here, but now you have exclusive access
  }

  // do more work here, without anyone having exclusive access
}

void unconditional_writer()
{
  boost::unique_lock< boost::shared_mutex > lock(_access);
  // do work here, with exclusive access
}

Also Note, unlike a shared_lock, only a single thread can acquire an upgrade_lock at one time, even when it isn't upgraded (which I thought was awkward when I ran into it). So, if all your readers are conditional writers, you need to find another solution.

Solution 3

Since C++ 17 (VS2015) you can use the standard for read-write locks:

#include <shared_mutex>

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

Lock myLock;


void ReadFunction()
{
    ReadLock r_lock(myLock);
    //Do reader stuff
}

void WriteFunction()
{
     WriteLock w_lock(myLock);
     //Do writer stuff
}

For older version, you can use boost with the same syntax:

#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>

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

Solution 4

Just to add some more empirical info, I have been investigating the whole issue of upgradable locks, and Example for boost shared_mutex (multiple reads/one write)? is a good answer adding the important info that only one thread can have an upgrade_lock even if it is not upgraded, that is important as it means you cannot upgrade from a shared lock to a unique lock without releasing the shared lock first. (This has been discussed elsewhere but the most interesting thread is here http://thread.gmane.org/gmane.comp.lib.boost.devel/214394)

However I did find an important (undocumented) difference between a thread waiting for an upgrade to a lock (ie needs to wait for all readers to release the shared lock) and a writer lock waiting for the same thing (ie a unique_lock).

  1. The thread that is waiting for a unique_lock on the shared_mutex blocks any new readers coming in, they have to wait for the writers request. This ensures readers do not starve writers (however I believe writers could starve readers).

  2. The thread that is waiting for an upgradeable_lock to upgrade allows other threads to get a shared lock, so this thread could be starved if readers are very frequent.

This is an important issue to consider, and probably should be documented.

Solution 5

Great response by Jim Morris, I stumbled upon this and it took me a while to figure. Here is some simple code that shows that after submitting a "request" for a unique_lock boost (version 1.54) blocks all shared_lock requests. This is very interesting as it seems to me that choosing between unique_lock and upgradeable_lock allows if we want write priority or no priority.

Also (1) in Jim Morris's post seems to contradict this: Boost shared_lock. Read preferred?

#include <iostream>
#include <boost/thread.hpp>

using namespace std;

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > UniqueLock;
typedef boost::shared_lock< Lock > SharedLock;

Lock tempLock;

void main2() {
    cout << "10" << endl;
    UniqueLock lock2(tempLock); // (2) queue for a unique lock
    cout << "11" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(1));
    lock2.unlock();
}

void main() {
    cout << "1" << endl;
    SharedLock lock1(tempLock); // (1) aquire a shared lock
    cout << "2" << endl;
    boost::thread tempThread(main2);
    cout << "3" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(3));
    cout << "4" << endl;
    SharedLock lock3(tempLock); // (3) try getting antoher shared lock, deadlock here
    cout << "5" << endl;
    lock1.unlock();
    lock3.unlock();
}
Share:
103,922
kevin42
Author by

kevin42

I'm just a boring guy that spends too much time designing and developing software.

Updated on August 18, 2020

Comments

  • kevin42
    kevin42 almost 4 years

    I have a multithreaded app that has to read some data often, and occasionally that data is updated. Right now a mutex keeps access to that data safe, but it's expensive because I would like multiple threads to be able to read simultaneously, and only lock them out when an update is needed (the updating thread could wait for the other threads to finish).

    I think this is what boost::shared_mutex is supposed to do, but I'm not clear on how to use it, and haven't found a clear example.

    Does anyone have a simple example I could use to get started?

  • Ken Smith
    Ken Smith about 14 years
    This is my first time using boost, and I'm a C++ newbie, so maybe there's something I'm missing -- but in my own code, I had to specify the type, like so: boost::shared_lock<shared_mutex> lock(_access);
  • hookenz
    hookenz almost 14 years
    I'm trying to use this myself but I'm getting an error. missing template arguments before 'lock'. Any ideas?
  • shaz
    shaz over 13 years
    Hi, are those locks scoped or would I need to explicitly release them? TIA
  • mmocny
    mmocny over 13 years
    @shaz Those are scoped, but you can release them early with .unlock() if you need.
  • mmocny
    mmocny about 13 years
    Just to comment on "another solution". When all my readers where conditional writers, what I did was have them always acquire a shared_lock, and when I needed to upgrade to write privilages, I would .unlock() the reader lock and acquire a new unique_lock. This will complicate the logic of your application, and there is now a window of opportunity for other writers to change the state from when you first read.
  • SteveWilkinson
    SteveWilkinson about 13 years
    Shouldn't the line boost::unique_lock< boost::shared_mutex > lock(lock); read boost::unique_lock< boost::shared_mutex > lock( _access ); ?
  • mmocny
    mmocny about 13 years
    I don't think so. Constructor: explicit upgrade_to_unique_lock(upgrade_lock<Lockable>& m_);
  • Ken Smith
    Ken Smith almost 13 years
    That last caveat is very strange. If only one thread can hold an upgrade_lock at a time, what's the difference between an upgrade_lock and a unique_lock?
  • mmocny
    mmocny over 12 years
    @Ken I was not very clear, but the benefit of upgrade_lock is it does not block if there are currently some shared_locks acquired (at least not until you upgrade to unique). However, the second thread to try and acquire an upgrade_lock will block, even if the first has not upgraded to unique, which I had not expected.
  • Ken Smith
    Ken Smith over 12 years
    @mmocny - Yeah, it seems like the actual range of uses for an upgrade_lock is pretty small. And given the overhead of a shared_lock, I suspect that in 90% of scenarios, a unique_lock is probably the right choice.
  • vines
    vines over 12 years
    I'd also say typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
  • Ofek Shilon
    Ofek Shilon almost 12 years
    (1) How do you make a writer decrement the count by an arbitrary amount atomically? (2) If the writer does somehow decrement the count to zero, how does it wait for already-running readers to finish before writing?
  • Ofek Shilon
    Ofek Shilon almost 12 years
    This is a known boost issue. It seems to be resolved at boost 1.50 beta: svn.boost.org/trac/boost/ticket/5516
  • Admin
    Admin over 11 years
    I've added the missing template arguments.
  • dale1209
    dale1209 over 10 years
    I'm actually having trouble figuring out why the above code deadlocks while the code in [stackoverflow.com/questions/12082405/… works.
  • JonasVautherin
    JonasVautherin over 10 years
    The Terekhov algorithm ensures that in 1., the writer cannot starve the readers. See this. But 2. is true. An upgrade_lock does not guarantee fairness. See this.
  • JonasVautherin
    JonasVautherin over 10 years
    It actually deadlocks in (2), not in (3), because (2) is waiting for (1) to release its lock. Remember: to get a unique lock, you need to wait for all the existing shared locks to finish.
  • Yochai Timmer
    Yochai Timmer over 10 years
    Don't need to include the whole thread.hpp . If you just need the locks, include the locks. It's not an internal implementation. Keep the includes to the minimal.
  • Guy
    Guy over 9 years
    @OfekShilon - thanks for linking to this interesting bug, however, I don't believe this is what mmocny meant in his comment. The scenario he's describing is simply the fact that upgradable lock is unique, and cannot be obtained by more than one thread at a time (which is by design)
  • Tim MB
    Tim MB almost 9 years
    Definitely the simplest implementation but I think it's confusing to refer to both mutexes and locks as Locks. A mutex is a mutex, a lock is something that maintains it in a locked state.
  • Tomáš Zato
    Tomáš Zato over 8 years
    I don't get it. Where do I release the locks?
  • Tomáš Zato
    Tomáš Zato over 8 years
    I get this error, I don't really understand what does it mean: 'boost::shared_lock<Mutex>::shared_lock(const boost::shared_lock<Mutex> &)' : cannot convert parameter 1 from 'const boost::shared_mutex' to 'const boost::shared_lock<Mutex> &'
  • Covi
    Covi over 8 years
    In the unconditional writer case, is using a boost::lock_guard in place of a boost::unique_lock also correct? Looking at this answer (stackoverflow.com/a/6731889/1165051), it seems the former is slightly more efficient -- but I want to make sure correctness is ensured in the case of read-write locking.
  • Caduchon
    Caduchon about 8 years
    Bad idea : If two writers try to access simultaneously, you can have a deadlock.
  • raaj
    raaj about 8 years
    what happens if I am reading halfway and a writer gets called
  • 1800 INFORMATION
    1800 INFORMATION about 8 years
    @raaj you can get the upgrade_lock, but upgrading to a unique lock will block until the shared_lock is released
  • SagiLow
    SagiLow almost 7 years
    @JonesV, even if (2) waits for all shared locks to finish, it wouldn't be a deadlock because it's a different thread than the one which acquired (1), if line (3) didn't exist, the program would finish with no deadlocks.