|Don't ask to ask, just ask|
Re: Recursive locks:killer application. Do they have one? (mu)by tye (Cardinal)
|on Feb 02, 2012 at 21:28 UTC||Need Help??|
I guess I'm in more of the opposite camp. The example problem given tells me that they are "doing it wrong", but not for using a re-entrant mutex. If you have a class where "A::foo() acquires the lock. It then calls B::bar()" then you are already holding the lock too long. The mutex being non-reentrant isn't going to point this out to you. B::bar() might decide to do something that blocks or that tries to acquire some other lock and then you've got lock-acquisition order to worry about which leads to deadlock problems.
I've seen tons of code that uses re-entrant mutexes and isn't "doing it wrong". That example is more like: you have a class that mostly just deals with the bits that need to be under a specific mutex. So the code to be run under the mutex is kept very small and cohesive by being its own class that just concentrates on doing the locking right.
And the re-entrant mutex comes in because you have methods that mostly can tell that they don't need to grab the mutex and so don't most of the time. So, since you are only grabbing the mutex in the rare cases when you need it, you can easily end up with a simple and clear utility method that might be called in a context where the mutex isn't held and also in contexts where the mutex is held and the utility function might (even indirectly) only rarely decide that it needs to hold the mutex.
You can get around that by splitting any such method into two methods, say doFooUnlocked() that just does the work and doFooLocked() that just holds the mutex and then calls doFooUnlocked(). Then doFooUnlocked() might be declared such that it can only be called from within the class. Then, if you already are holding the mutex, you need to call doFooUnlocked().
But, that solution requires the bifurcation of all methods that might call doFoo*() which can lead to quite a mess.
But this type of concern mostly only pops up when doing the style of threading + locking that Java pretty much encourages and I find that that is an approach that is just way too easy to end up becoming an unreliable mess after it tries to scale in the feature set supported. So I don't do anything like that these days.
I wish I had a much more concrete example handy but it has been too many years since I was doing that type of work (in C++).