57

Is there a C++11 equivalent for the boost::shared_mutex. Or another solution to handle a multiple reader / single writer situation in C++11?

Haatschii
  • 9,021
  • 10
  • 58
  • 95
  • 16
    `boost::shared_mutex` was rejected by the standardization committee. This might be relevant: http://permalink.gmane.org/gmane.comp.lib.boost.devel/211180 – Andy Prowl Jan 13 '13 at 18:41
  • @AndyProwl: that is an awesome argument against `boost::shared_mutex` by Anthony. thanks for sharing it. – Nawaz Jan 13 '13 at 18:48
  • 3
    @AndyProwl: I second Nawaz on that. – Matthieu M. Jan 13 '13 at 18:50
  • 4
    Agreed that shared locking is not a good solution for when you are able to hold the lock for a tiny bit of time. But that is not always possible. If it were, shared locking would not be so prevalent across so many libraries and languages. – Howard Hinnant Jan 13 '13 at 19:15
  • 3
    If we're taking sides, I'm on Howard's. In particular I find "time consuming operations whilst holding a lock is a design smell" wholly unconvincing. Either "design smell" means, "something that must never happen", in which case it is certainly false IME (considering that "enough to avoid severe cache ping-pong" is not a *lot* of time to consume), or else "design smell" means "a worrying sign that there may be a problem, but something that nonetheless is necessary in certain circumstances", in which case why withdraw support for those circumstances by removing rwlocks from the proposal? – Steve Jessop Jan 13 '13 at 19:39
  • I find this interesting and convincing (emphasize mine) : *"Also, the shared_mutex is a point of contention, and **thus not scalable**. Locking a shared_mutex necessarily modifies the state of the mutex, even for a read lock. Consequently, the cache line holding the shared_mutex state must be transferred to whichever processor is performing a lock or unlock operation. **If you have a lot of threads performing frequent, short read operations, then on a multiprocessor system this can lead to a lot of cache ping-pong**, which will considerably impact the performance of the system."* – Nawaz Jan 13 '13 at 19:42
  • 5
    @Nawaz: but that's just an argument that there exist circumstances where a rwlock is no better than a mutex (and elsewhere Willams explains why sometimes it's worse). This is true, but it's not a good reason to remove rwlocks. You might as well remove `vector` on the basis that *sometimes* a `deque` is better. I'm not saying the committee didn't have good reasons not to include `shared_mutex`, just that this explanation isn't (I hope) all there is to it. Sometimes your locked ops are an order of magnitude slower than cache flush, so are *not* serialized by a rwlock. Doesn't make them "smelly". – Steve Jessop Jan 13 '13 at 19:44
  • @SteveJessop: I cannot comment on it's removal decision, because I don't know how bad is bad for the standard. Honestly speaking, I find that argument *more interesting* than convincing; that doesn't mean I find it unconvincing. It just seems very plausible to me. Even [this](http://www.bluebytesoftware.com/blog/2009/02/12/ReaderwriterLocksAndTheirLackOfApplicabilityToFinegrainedSynchronization.aspx) concludes ***"Sharing is evil, fundamentally limits scalability, and is best avoided."*** (though for .NET, but that makes sense to me in general). – Nawaz Jan 13 '13 at 19:52
  • 2
    The committee's decision to forgo read/write locks in C++11 was based on non-technical arguments. This was part of the 2007 Kona compromise. This compromise removed read/write locking and thread pools from consideration for C++0x in the hopes of limiting committee work to make C++09 possible. Instead we got the worst of both: A standard 2 years late and without needed features. Although a reasonable argument could be made that we would have been later than 2 years had we not made that compromise. At any rate, scheduling is the reason, nothing more. – Howard Hinnant Jan 13 '13 at 19:53
  • @Nawaz: sure. There are lots of things that are fundamentally evil, limit scalability and are best avoided. For example, consuming RAM (by paging in memory), or opening sockets (takes a port). But as programmers, we find ourselves doing them anyway, so it is (or would be) useful to have a standard mechanism for it ;-) – Steve Jessop Jan 14 '13 at 10:03
  • possible duplicate of [How would a readers/writer lock be implemented in C++11?](http://stackoverflow.com/questions/12033188/how-would-a-readers-writer-lock-be-implemented-in-c11) – jww Feb 21 '14 at 22:08

3 Answers3

71

I tried but failed to get shared_mutex into C++11. It has been proposed for a future standard. The proposal is here.

Edit: A revised version (N3659) was accepted for C++14.

Here is an implementation:

http://howardhinnant.github.io/shared_mutex

http://howardhinnant.github.io/shared_mutex.cpp

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
19

Simple... There isn't one. There is no standard C++ implementation of a readers-writer lock.

But, you have a few options here.

  1. You are left at your own devices to make your own readers-writer lock.
  2. Use a platform-specific implementation such as Win32's, POSIX's, or Boost's as you mention.
  3. Don't use one at all -- use a mutex which already exists in C++11.

Going with #1 and implementing your own is a scary undertaking and it is possible to riddle your code with race conditions if you don't get it right. There is a reference implemenation that may make the job a bit easier.

If you want platform independent code or don't want to include any extra libraries in your code for something as simple as a reader-writer lock, you can throw #2 out the window.

And, #3 has a couple caveats that most people don't realize: Using a reader-writer lock is often less performant, and has more difficult-to-understand code than an equivalent implementation using a simple mutex. This is because of the extra book-keeping that has to go on behind the scenes of a readers-writer lock implementation.


I can only present you your options, really it is up to you to weigh the costs and benefits of each and pick which works best.


Edit: C++17 now has a shared_mutex type for situations where the benefits of having multiple concurrent readers outweigh the performance cost of the shared_mutex itself.

Sean Cline
  • 6,979
  • 1
  • 37
  • 50
13

No, there is no equivalent for boost::shared_mutex in C++11.

Read/writer locks are supported in C++14 or later, though:

The difference is that std::shared_timed_mutex adds additional timing operations. It implements the SharedTimedMutex concept, which is an extension of the simpler TimedMutex concept implemented by std::shared_mutex.


Keep in mind that acquiring a lock for a read/writer mutex is more costly than acquiring a normal std::mutex. As a consequence, a read/writer mutex will not improve the performance if you have frequent, but short read operations. It is better suited for scenarios were read operations are frequent and expensive. To quote from Anthony Williams' post:

The cost of locking a shared_mutex is higher than that of locking a plain std::mutex, even for the reader threads. This is a necessary part of the functionality --- there are more possible states of a shared_mutex than a mutex, and the code must handle them correctly. This cost comes in both the size of the object (which in both your implementation and my POSIX implementation includes both a plain mutex and a condition variable), and in the performance of the lock and unlock operations.

Also, the shared_mutex is a point of contention, and thus not scalable. Locking a shared_mutex necessarily modifies the state of the mutex, even for a read lock. Consequently, the cache line holding the shared_mutex state must be transferred to whichever processor is performing a lock or unlock operation.

If you have a lot of threads performing frequent, short read operations, then on a multiprocessor system this can lead to a lot of cache ping-pong, which will considerably impact the performance of the system. In this case, you may as well adopt the simpler design of just using a plain mutex, as the readers are essentially serialized anyway.

If the reads are not frequent, then there is no contention, so you don't need to worry about concurrent readers, and a plain mutex will suffice for that scenario anyway.

If the read operations are time consuming, then the consequence of this contention is less visible, since it is dwarfed by the time spent whilst holding the read lock. However, performing time consuming operations whilst holding a lock is a design smell.

In the vast majority of cases, I think that there are better alternatives to a shared_mutex. These may be a plain mutex, the atomic support of shared_ptr, the use of a carefully constructed concurrent container, or something else, depending on context.

Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239