I've seen an example for a shared mutex:
class MyData {
std::vector<double> data_;
mutable shared_mutex mut_; // the mutex to protect data_;
public:
void write() {
unique_lock<shared_mutex> lk(mut_);
// ... write to data_ ...
}
void read() const {
shared_lock<shared_mutex> lk(mut_);
// ... read the data ...
}
};
Naturally I would have written instead:
public:
void write() {
mut_.lock();
// ... write to data_ ...
mut_.unlock();
}
void read() const {
mut_.lock_shared();
// ... read the data ...
mut_.unlock_shared();
}
};
Is my way also correct? And is there a difference between what I used and what was used in the example? Also, are there advantages of one over the other? Thank you!
Is my way also correct?
Consider what would happen if the code between the locking of the mutex and the unlocking throws an exception:
void write() {
mut_.lock();
// <-- exception is thrown here
mut_.unlock();
}
The mutex then remains locked.
are there advantages of one over the other?
Yes, unique_lock<>
follows the RAII idiom, and therefore unlocking of the mutex is handled automatically (i.e., by its destructor) in case of an exception:
void write() {
unique_lock<shared_mutex> lk(mut_);
// <-- exception is thrown
}
In case of an exception after the creation of the unique_lock<shared_mutex>
object – lk
– its destructor is called, and it then unlocks the associated mutex if it was locked (remember that std::unique_lock
, unlike std::lock_guard
, doesn't always have ownership of the lock on the associated mutex – see std::defer_lock
and std::unique_lock::unlock()
).
To sum up, with lock_guard
/unique_lock
/shared_lock
, no special handling is required from your side in case of exceptions or when leaving the member function from different execution paths.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With