10 ms·
In the C++ bag of tricks: scoped locks
- infogulch 13y agoWhat about: #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.
- JoeAltmaier 13y agoWorks, but requires {} for scope. The local-object version can be scoped however you like. It optimizes away just fine, similar to the for loop.
- ef47d35620c1 13y agoC++11 has scoped locks built-in, doesn't it?
- steve-howard 13y agoIt offers a variety of them, simplest of which is lock_guard: http://en.cppreference.com/w/cpp/thread/lock_guard http://en.cppreference.com/w/cpp/thread/lock_guard
- vinkelhake 13y agoThe real advantage of a scoped lock is that it promises to release the mutex, no matter how the scope is exited. If this LOCKED macro is used and an exception is thrown from within the block, that mutex now stays locked. > 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.
- stinos 13y agoThis. For at least three typical 'best practice' reasons. I know there are times best practices can be nicely ignored, but this is not one of those occasions. On the contrary. Let me lay them out again: - these are the days of C++11 where scoped locks are readily available - premature optimization - macros are evil
- pjmlp 13y agoOne of the reasons C++ gets bad reputation, is those Cisms that people insist on writing, when the language offers much better alternatives.
- stinos 13y agoWhich is mostly due to lack of education. If you don't ever spent time learning, but just coding, you don't know better. And sometimes it's all too easy to get thrapped into it due to deadlines and whatnot. A former collegue of mine, unfortunately, was the best example: programmed for like 15 years years and still wrote like it was all 'C with classes' (not that there ever was such a thing). You know, writing declarations at the top of functions etc. Missing out on C++11 is just as bad.
- pjmlp 13y agoFully agree. Personally I never liked straight C. For me it was just a short transition between Turbo Pascal and C++, only to be used when required to do so. Around 1993 or so. I devoured every book and publication about C++ I could put my hands on. Always trying to educate others how to write safe and portable C++. Nowadays I am into JVM and .NET mostly, but when I reach for C++ it is with regard to the latest standards. Every now and then, I still see C++ code that is basically the C subset without using any improvements of C++ over C.
- dmm 13y agoC with classes is what the google c++ style guide mandates: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml http://google-styleguide.googlecode.com/svn/trunk/cppguide.x... No exceptions, no boost, no lambda, no rtti, streams only for logging, avoid operator overloading, etc.
- dllthomas 13y agoYou can do some of this with the cleanup attribute in GCC, although it doesn't trigger in the case of a longjmp.
- khc 13y agoif you can rely on gcc, cleanup attribute is probably the best you can do. The solution in original post doesn't cope with "break", "continue", "return" (and of course, "goto", but I don't remember if cleanup attribute handles that either). Looks like their project already uses C++ though, so why don't they just use C++ for this is beyond me.
- dllthomas 13y agoAgreed.
- deleted 13y ago[deleted]
- deleted 13y ago[deleted]
- dllthomas 13y agoActually, it looks like C++ destructors don't run on longjmp either, though that's less of an issue since C++ actually has real exceptions.
- mpyne 13y agolongjmp is what the library and standards writers refer to as a "non-local goto" so it's kind of to be expected that it would manage to bypass destructors (I'm not sure how you'd unwind the stack enough to ensure that you've destructed everything created since the corresponding setjmp).
- dllthomas 13y agoIt's not terribly surprising, but it is worth noting since one use of setjmp/longjmp is to implement exceptions, and one would expect (and desire) destructors to run when unwinding on exceptions.
- steveklabnik 13y agoThis kind of thing is one area I'm excited to see work in Rust: you can use Rust's safety guarantees to ensure that all access goes through the lock.
- fighterjet 13y agoI fail to see the point of using a macro (ew) to accomplish something that's included, with several different implementations, in the c++11 standard library
- taway2012 13y agoDon't do this. The whole thing gave off a bad smell on first reading so I would never have done this. 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.
- VikingCoder 13y agoYou're also consuming the variable name i, which everyone else is probably using. Speaking of which, this means you can't have one of your locks scoped inside another one.
- JoeAltmaier 13y agoSUre you could. The{} make it work.
- VikingCoder 13y agoEDIT: I sit corrected. My post is wrong. 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.
- plorkyeran 13y agoC++ has variable shadowing, so that works correctly. for (int i = 0; i < 5; ++i) { for (int i = 0; i < 5; ++i) { printf("hello\n"); } } prints hello 25 times.
- VikingCoder 13y agoOh right. My brain was stuck in another language. Wow, I forgot how much I'd forgotten about C++.
- JoeAltmaier 13y agoActually that's just C (name nesting)
- cjensen 13y agoSolvable by creating a Preprocessor macro which creates a unique and complex-named identifier. #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.
- pgs_pants 13y agoThis is good, but probably wasted here as the folks here will just complain because this is no Go or haskell.
- blt 13y agoThis is not exception-safe like scoped locks are.
- jacobparker 13y agoException issues aside, an odd definition of "scoped". A very non-C++ solution. Use RAII and lock_guard (if possible) as vinkelhake demonstrates. Consider, LOCKED(lk_var) { return 42; // whoops }
- 8_hours_ago 13y agoA very important gotcha with this approach is that any variable named "i" will be reset to 0 inside of the lock, and modifying "i" inside of the lock can cause an infinite loop. This can be mitigated by generating a unique variable name with the C preprocessor: http://stackoverflow.com/questions/1132751/how-can-i-generate-unique-values-in-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.
- ska 13y agoPeople have already pointed out the specific problems with this approach so I won't repeat them. 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.
- deleted 13y ago[deleted]
- cbsmith 13y agoBoost.Scopexit addresses this point pretty well I think: http://www.boost.org/doc/libs/1_55_0/libs/scope_exit/doc/html/index.html http://www.boost.org/doc/libs/1_55_0/libs/scope_exit/doc/htm...
- mojuba 13y agoWouldn't this be exception-safe, provided lock_iter defines a ctor, dtor, operator bool() and operator ++()? #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)
- ambrop7 13y agoI learned about the for loop hack from avr-libc[1][2]. It even does some preprocessor abuse to implement different kinds of atomic and non-atomic sections. [1] http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html http://www.nongnu.org/avr-libc/user-manual/group__util__atom... [2] http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include/util/atomic.h?root=avr-libc&view=markup http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include...
- zeroonetwothree 13y agofolly::Synchronized implements this in a correct way (and also prevents access without acquiring the lock): SYNCHRONIZED(data) { // modify data } https://github.com/facebook/folly/blob/master/folly/Synchronized.h https://github.com/facebook/folly/blob/master/folly/Synchron...