0

std::shared_mutex can provide an exclusive lock like std::mutex. Plus it extends the synchronization capabilities by allowing the parallel read-only accesses to the multiple threads.

Under the hood the number of machine instructions might be lesser for std::mutex. But at a practical level, while reading about their usage I feel that std::shared_mutex can potentially supersede or even deprecate std::mutex.

In other words, is there any place where std::mutex has to be used over std::shared_mutex?

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • idk. Lets imagine you have an object with a (toy example) `mutable size_t counter;` which is incremented each time a `const` qualified method is called. With `std::shared_mutex` invoking such a method would yield UB (data race). But exclusive ownership prevents it – Sergey Kolesnik Jul 28 '23 at 15:58
  • 1
    @SergeyKolesnik: That represents an API that's actively lying to you. If a function has `const` access to some object, it *ought* to ensure that there are no data races. If that object stores some `mutable` member that gets modified in `const` functions, the onus is on *those functions* to synchronize access to the `mutable` member. – Nicol Bolas Jul 28 '23 at 16:08
  • @NicolBolas not necessarily. One can design a class with no concurrent access in mind. I don't advocate for thoughless usage of `mutable`, but this situation is not very far from "probalbe". And with a tendency for some (even famous) libraries to overuse `pImpl` idion, you never can tell for yourself. Hence, in production code beter to use `std::mutex` if you are not entirely sure about thread safety – Sergey Kolesnik Jul 28 '23 at 16:14
  • @SergeyKolesnik: "*One can design a class with no concurrent access in mind.*" Then it should be part of that class's documentation that it should not be concurrently accessed *at all*. "*if you are not entirely sure about thread safety*" If you're not "entirely sure about thread safety" then you *don't have thread safety*. – Nicol Bolas Jul 28 '23 at 16:19
  • 1
    Related: https://stackoverflow.com/q/39496504 – Artyer Jul 28 '23 at 19:26
  • Also, the [*SharedMutex*](https://en.cppreference.com/w/cpp/named_req/SharedMutex) requirement is a strict superset of [*Mutex*](https://en.cppreference.com/w/cpp/named_req/Mutex). You could even have a `std::shared_timed_mutex`, but as long as you only used it as a *Mutex*, it would have the desired semantics (if only less efficiently). – Artyer Jul 28 '23 at 19:33

2 Answers2

3

std::vector is a functional superset of std::array. Anything an array can do, a vector can do as well.

But that's not a good excuse to avoid using array when it is obviously appropriate (ie: statically sized, no need for heap allocation, etc).

Likewise, shared_mutex could be used in any place that mutex could. But that doesn't mean you should.

The types you use communicates something to the people who read your code. If you use shared_ptr, you are communicating to users that ownership of this object is being shared. That's what the name means. Using shared_ptr when you only use it for unique ownership of a resource says either that you expect to share it in the future or you don't know what you're doing.

Using shared_mutex communicates that users are expected to acquire shared locks to this mutex. This says something about the access patterns of code trying to access the guarded data. It means that the data will be read concurrently by a lot of code, but will only occasionally need exclusive access by some restricted subset of code.

And as with shared_ptr, using it in cases where this does not happen is a miscommunication of what is going on in your code.

And that ignores the performance implications. shared_mutex and its locks aren't free, after all. Any "practical level" should not throw performance away.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Though I get what you want to say, the equating between `std::array` and `std::vector` is a wrong example, at least for the future visitors. They are quite different (static vs dynamic). It cannot be treated like `std::mutex` and `std::shared_mutex`. I am comparing "red apple" and "green apple", where you are comparing "apple" and "pine-apple". – iammilind Jul 29 '23 at 02:57
  • @iammilind: "*They are quite different (static vs dynamic).*" Anyplace you could use a static array is a place you could use a *dynamic* one. Being static is just a special case of dynamic. Just like being not shared is a special case of being shared. – Nicol Bolas Jul 29 '23 at 04:13
2

Yesstd::shared_mutex is a powerful synchronization primitive that can be used to implement both read/write locks and exclusive locks which makes it more flexible than std::mutex in many ways.there are still several scenarios where std::mutex would be preferable over std::shared_mutex.

  • Performance: std::mutex is often simpler and thus faster than std::shared_mutex. A std::shared_mutex typically requires more overhead to manage shared access rights. If you don't need the ability to allow multiple simultaneous readers, the simpler std::mutex will often be faster.

  • Memory: std::shared_mutex can consume more memory than std::mutex due to the extra data needed to manage multiple reader threads.

  • Concurrency Scenario: If the lock is mostly used for writing rather than reading, a simple std::mutex could be preferable since the use case does not really benefit from the std::shared_mutex functionality.

  • Complexity: std::shared_mutex comes with more operations (lock, try_lock, unlock, lock_shared, try_lock_shared, unlock_shared) which could add unnecessary complexity to the code if only mutual exclusion is required.

So, std::shared_mutex can offer benefits in terms of allowing multiple readers, if your primary use case is simply to provide exclusive access to a resource std::mutex could still be a simpler and more efficient choice.

Akhilesh Pandey
  • 855
  • 5
  • 10