I think you are misunderstanding the scope of the deadlock avoidance. That’s understandable since the text seems to mention lock
in two different contexts, the “multi-lock” std::lock
and the individual locks carried out by that “multi-lock” (however the lockables implement it). The text for std::lock
states:
All arguments are locked via a sequence of calls to lock(), try_lock(),or unlock() on each argument. The sequence of calls shall not result in deadlock
If you call std::lock
passing ten different lockables, the standard guarantees no deadlock for that call. It’s not guaranteed that deadlock is avoided if you lock the lockables outside the control of std::lock
. That means thread 1 locking A then B can deadlock against thread 2 locking B then A. That was the case in your original third example, which had (pseudo-code):
Thread 1 Thread 2
lock A lock B
lock B lock A
As that couldn’t have been std::lock
(it only locked one resource), it must have been something like unique_lock
.
The deadlock avoidance will occur if both threads attempt to lock A/B and B/A in a single call to std::lock
, as per your first example. Your second example won’t deadlock either since thread 1 will be backing off if the second lock is needed by a thread 2 already having the first lock. Your updated third example:
Thread 1 Thread 2
std::lock(lock1,lock2); std::lock(lock3,lock4);
std::lock(lock3,lock4); std::lock(lock1,lock2);
still has the possibility of deadlock since the atomicity of the lock is a single call to std::lock
. For example, if thread 1 successfully locks lock1
and lock2
, then thread 2 successfully locks lock3
and lock4
, deadlock will ensue as both threads attempt to lock the resource held by the other.
So, in answer to your specific questions:
1/ Yes, I think you’ve misunderstood what the standard is saying. The sequence it talks about is clearly the sequence of locks carried out on the individual lockables passed to a single std::lock
.
2/ As to what they were thinking, it’s sometimes hard to tell 🙂 But I would posit that they wanted to give us capabilities that we would otherwise have to write ourselves. Yes, back-off-and-retry may not be an ideal strategy but, if you need the deadlock avoidance functionality, you may have to pay the price. Better for the implementation to provide it rather than it having to be written over and over again by developers.
3/ No, there’s no need to avoid it. I don’t think I’ve ever found myself in a situation where simple manual ordering of locks wasn’t possible but I don’t discount the possibility. If you do find yourself in that situation, this can assist (so you don’t have to code up your own deadlock avoidance stuff).
In regard to the comments that back-off-and-retry is a problematic strategy, yes, that’s correct. But you may be missing the point that it may be necessary if, for example, you cannot enforce the ordering of the locks before-hand.
And it doesn’t have to be as bad as you think. Because the locks can be done in any order by std::lock
, there’s nothing stopping the implementation from re-ordering after each backoff to bring the “failing” lockable to the front of the list. That would mean those that were locked would tend to gather at the front, so that the std::lock
would be less likely to be claiming resources unnecessarily.
Consider the call std::lock (a, b, c, d, e, f)
in which f
was the only lockable that was already locked. In the first lock attempt, that call would lock a
through e
then “fail” on f
.
Following the back-off (unlocking a
through e
), the list to lock would be changed to f, a, b, c, d, e
so that subsequent iterations would be less likely to unnecessarily lock. That’s not fool-proof since other resources may be locked or unlocked between iterations, but it tends towards success.
In fact, it may even order the list initially by checking the states of all lockables so that all those currently locked are up the front. That would start the “tending toward success” operation earlier in the process.
That’s just one strategy, there may well be others, even better. That’s why the standard didn’t mandate how it was to be done, on the off-chance there may be some genius out there who comes up with a better way.