> The only gotcha is, there is some overhead involved in this approach. The class instance takes up some space on the stack (several bytes) for every lock acquisition.
Not so fast. A decent compiler will eliminate the overhead and not actually allocate stack space for the lock. Here's an example using one of the lock guards in C++:
struct foo {
int var;
std::mutex m;
};
void process1(foo& f) {
std::lock_guard<std::mutex> lock(f.m);
++f.var;
}
void process2(foo& f) {
f.m.lock();
++f.var;
f.m.unlock();
}
GCC 4.8.1 generates identical code for process1 and process2.(1) avoids best practice (RAII) using...
(2) a flawed rationale (ignoring the existence of compiler optimizations)...
(3) without measurement to demonstrate the supposed flaw in the best practice (which would have surfaced the flawed rationale)...
(4) and therefore implements a micro-optimization with no actual win...
(5) and introduces a bug in the process (it's not exception safe).
- these are the days of C++11 where scoped locks are readily available
- premature optimization
- macros are evil
As a general rule, if your C++ compiler can statically see the layout of your structures and the implementation of your functions, it will produce code as efficient as inlining everything yourself. Sufficiently complex functions make the benefits of inlining a judgment call on the compiler's part, but for something as simple as an RAII scoped lock, the abstraction is free.
Thank you vinkelhake for nailing the the real problem (exceptions) and bothering to check whether the stack overhead really exists.
Just wanted to chime in with a comment because HN doesn't show vote counts, so future readers will "see" my upvote on vinkelhake's comment.
LOCKED(lk_var) {
return 42; // whoops
}Speaking of which, this means you can't have one of your locks scoped inside another one.
#define LOCKED(lk) for(int UNIQUE_ID=0; UNIQUE_ID<1 && !lk_lock(lk); lk_unlock(lk), UNIQUE_ID++)
Use __LINE__ to help make the id unique. lock(lk_var) lock(lk_var2)
{
lk_var = lk_var2;
}
Or this: lock(lk_var) { lock(lk_var2 { lk_var = lk_var2; } }
Using the pre-processor to solve a regular, every day, hum-drum, solved-a-billion-times RAII problem is bad news.No, you can't, and no they don't.
lock(lk_var) {
lock(lk_var2) {
var = var2;
}
}
That expands to: for(int i=0; (i < 1) && !lk_lock(lk_var); lk_unlock(lk_var), i++)
{
for(int i=0; (i < 1) && !lk_lock(lk_var2); lk_unlock(lk_var2), i++)
{
var = var2;
}
}
You end up with an i scoped inside another i. #define LOCKED(lk) for(int i=lk_lock(lk); i == 0; lk_unlock(lk), i++)
Probably better to test for 0 exactly, in case it fails. You don't want it to unlock twice just because it returns -1.This can be mitigated by generating a unique variable name with the C preprocessor: http://stackoverflow.com/questions/1132751/how-can-i-generat...
That being said, it's much better to use a standard method of scoped locking instead of trying to reinvent the wheel.
[1] http://www.nongnu.org/avr-libc/user-manual/group__util__atom...
[2] http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include...
#define LOCKED(lk) for (lock_iter i = lk; i; i++)
(Edit: I thought lock_iter could be an empty class but no, it can not, so I edited my post. lock_iter will have an internal flag that should be optimized away following the same logic int i is optimized in the OP's version)It's worth reiterating, though: If you are writing c++ these days and find yourself reaching for a preprocessor macro, I think that's a good sign you should think really carefully about what you are actually trying to do. Chances are really good there is a (much) better way.
Looks like their project already uses C++ though, so why don't they just use C++ for this is beyond me.
SYNCHRONIZED(data) { // modify data }
https://github.com/facebook/folly/blob/master/folly/Synchron...