Reputation: 3356
For now I have this implementation of thread-safe field getting from a class:
int A::GetValue() const
{
_mutex.Lock();
int temp = _value;
_mutex.Unlock();
return temp;
}
I want to replace it with lock/scope guard implementation like this (LockGuard class has only constructor/destructor and Mutex * _mutex field):
LockGuard::LockGuard(Mutex & mutex) : _mutex(&mutex)
{
_mutex->Lock();
}
LockGuard::~LockGuard()
{
_mutex->Unlock();
}
And refactored code:
int A::GetValue() const
{
LockGuard lockGuard(_mutex);
return _value;
}
I suddendly realized that I don't sure if this a safe implementation. Is it guaranteed that firstly the copy of _value will be passed out of function and only with this copy already existed the _mutex will be unlocked? Please don't provide C++11 alternative implementation examples - I have embedded system and unfortunatelly can't use them.
Upvotes: 1
Views: 628
Reputation: 7625
Your implementation of LockGuard
is ok, but usage is wrong. With the statement
LockGuard(_mutex);
You are creating a temporary LockGuard
which will be destroyed just after the statement is finished.
Your usage should be
LockGuard guard(_mutex);
return _value;
LockGuard guard(_mutex);
will create a LockGuard
object, which will lock the mutex in constructor. When the method returns, the destructor will be called, unlocking the mutex. And you no longer have to use a temporary to return.
Upvotes: 4
Reputation: 5102
Perhaps this program my help you visualize what happens:
#include <iostream>
// Replaces the int so that we know when things happen
struct Int {
Int() {
std::cout << "Int::Int()" << std::endl;
}
~Int() {
std::cout << "Int::~Int()" << std::endl;
}
Int(const Int &x) {
std::cout << "Int::Int(const Int&)" << std::endl;
}
};
struct LockGuard {
LockGuard() {
std::cout << "Locking" << std::endl;
}
~LockGuard() {
std::cout << "Unlocking" << std::endl;
}
};
struct A {
Int getValue() const {
LockGuard lockGuard;
return _value;
}
Int _value;
};
int main() {
A a;
std::cout << "about to call" << std::endl;
Int x=a.getValue();
std::cout << "done calling" << std::endl;
}
That results in
Int::Int()
about to call
Locking
Int::Int(const Int&)
Unlocking
done calling
Int::~Int()
Int::~Int()
One final note is that you might need to make _mutex
mutable
in your attribute declaration as locking and unlocking are typically non-const operation that would not be allowed in a const method otherwise.
Upvotes: 4
Reputation: 73379
This line is a problem:
LockGuard(_mutex);
It should be:
LockGuard foo(_mutex);
Otherwise it will go out of scope and unlock your mutex before the rest of the method executes. (you can verify the problematic behavior by temporarily inserting printf's or similar in the LockGuard constructor and destructor as well as in your calling method).
Other than that your locking pattern is good.
Upvotes: 5