views:

106

answers:

4

Is it necessary to use a temporary here to be thread-safe?

 int getVal() {
       this->_mutex.lock();
       int result = this->_val;
       this->_mutex.unlock();
       return result;
 }

I'll give you disassembly of simple RAII test function

int test()
{
    RAIITest raii; //let's say it's a scoped lock
    return 3;
}


 {
     0x004013ce <_Z4testv>:    push  %ebp
     0x004013cf <_Z4testv+1>:  mov   %esp,%ebp
     0x004013d1 <_Z4testv+3>:  sub   $0x28,%esp
     return 3;
     0x004013d4 <_Z4testv+6>:  lea   -0x18(%ebp),%eax
     0x004013d7 <_Z4testv+9>:  mov   %eax,(%esp)
     0x004013da <_Z4testv+12>: call  0x4167a0 <_ZN8RAIITestD1Ev>  //here destructor is called
     0x004013df <_Z4testv+17>: mov   $0x3,%eax //here result is pushed onto the stack
 }
 0x004013e4 <_Z4testv+22>: leave 
 0x004013e5 <_Z4testv+23>: ret   

the compiler is gcc/g++ 3.4.5

+5  A: 

If access to this->_val is synchronized by this->_mutex, then you don't have a choice the way the code is written currently. You need to read this->_val before you unlock the mutex and you have to unlock the mutex before you return. The result variable is necessary to get this order of actions.

If you use a lock_guard (or scoped_lock in Boost), then you don't need to use the temporary because the lock on the mutex will be released when the function returns. For example, using the C++0x threads library:

int getVal() {
    std::lock_guard<std::mutex> lock(_mutex);
    return this->_val;
}   // lock is released by lock_guard destructor 
James McNellis
No it won't. If this->_val is a builtin result will be exactly the same. And object (scoped_lock) is destructed before the result is pushed onto the stack.
doc
@doc: What won't? The local variable `lock` will not be destroyed until after the copy of the object to be returned is made.
James McNellis
take a look at dissasembly I've added to my question
doc
@doc: I compiled James's code with assembly language output: it reads the member variable to %ebx, then it calls the destructor of lock_guard, then it copies %ebx to %eax and cleans up stack/returns. gcc 4.4.4
Cubbi
@Cubbi: so it should be thread-safe? I wonder if older compilers are aware to this. Another interesting thing is when `_val` is a pointer (and the function returns so), but that's a different topic :)
doc
I'll accept your answer together with Cubbi's comment
doc
@doc: your disassembly has nothing to do with James's code. You're listing the asm for a different (and flawed) test program.
jalf
+1  A: 

Yes if you use explicit lock()/unlock(). No if you construct a lock object on the stack and have its destructor do the unlock.

usta
why? Object is destructed before the result is pushed due to return.
doc
That's not the case. Consider this code:std::string f(){ std::string ret = "abc"; return ret;}If 'ret' were destroyed before return statement copied it as the function's return value, f() could return a garbage for example, if at all.
usta
@usta it's easier to pick intrinsic type. Construction, destruction, these are high-level terms. Behind the scenes result is usually pushed on the function call stack defined by a certain ABI. And what bothers me, is that destructor of mutex is called before the result is there.
doc
@doc I don't see what ABI has to do with *when* destructors of local objects are called. In your simple example the compiler can choose to move 3 to the return value register later, because you have just return 3; and with a literal it won't matter anyway. We see the 'as-if' rule in action. But with a more elaborate sample, you'll see destructor isn't called until after return value is copied. FYI, I just tried your original example, modified to use boost::lock_guard, with vc10 and the disassembly shows the destructor is "called" after this->_val is copied.
usta
Note: in my previous comment I placed "called" in quotes as it got inlined, so there was not a real call, but that's irrelevant here...
usta
@usta: I admit, but that simple return made me suspicious. And a copy is made twice - once before destructor is called and then result is put onto the stack, which may be seen as making a second copy (in my simple example compiler chosed registry but on more elaborate example you would see a stack). If the first copy is guaranteed then OK, that's all I just wanted to hear.
doc
I'll give you +1 for a better mood ;)
doc
@doc Thanks :) Yes, the first copy is guaranteed. If that first copy is to a register, then it might be necessary to copy from that register to stack before the call to destructor as that call may change the values of registers. Then after the call there may even be a third copy from stack to a register if the function should place its return value in a register rather than stack. That's why in your "return 3;" example the compiler chose to store the return value later, so as to avoid those extra copies. But it can't do the same when return value is read from a variable, for example.
usta
A: 

No -- the compiler creates a temporary for the return value automatically. You don't normally need to protect a read with a mutex either, so even though its multithreaded, just return _val; should be sufficient.

As an aside, I'd get rid of the leading underscore though -- the rules about what variable names you can and cannot use when they start with an underscore are sufficiently complex that it's better to just avoid them entirely.

Jerry Coffin
Jerry your comment re not needing lock for read applies because this is an int, is that correct? For more complex types you would not want concurrent writes going on at the same time as copy construction I would have thought. Thanks.
Steve Townsend
@Steve: Yes. Of course, nothing in the standard says it has to be that way (since the standard doesn't cover threading at all), but from a practical viewpoint, an int is essentially always atomic.
Jerry Coffin
@Jerry Coffin but "copying" of that temporary happens after the mutex is unlocked. If then a thread will be suspended and the other thread will modify `_val` (I put underscores in this example to point out that it's a class member (but then I've added `this->` to even point it out better) so forgive me) during the "copying", then result may potentialy become rubish. I know that in practice this shouldn't happen but I'd like to know what's trully thread-safe.
doc
A: 

You can do this cleanly is your mutex is encapsulated in a scoped_lock that unlocks on destruction:

 int getVal() {
       scoped_lock lockit(_mutex);
       return _val;
 }

And yes, you do need to hold the lock until it's returned.

Steve Townsend
In case of _val being builtin type, disassembly will look exactly the same as in the case of non-scoped locks
doc