First of all, I assume that the question is to avoid deadlocks when locking multiple arbitrary mutexes. It is important to always use the same ordering agreement throughout the code, using a set of mutexes. If you can guarantee that mutex A will always be locked to B, B will always be locked to C, and A always to C, you will avoid blocking.
In the first code example, the convention is to first lock the mutex with the lowest memory address. This will work fine, as address ordering is invariant. The second version is the official Boost method , avoiding deadlocks. The documentation does not indicate which orders are executed internally. I do not recommend looking for it in the source and using this information elsewhere in your code - it can subtly break things if the library changes.
If you start from scratch (before you have not used more than one mutex in your code), using the Boost method is definitely preferable - you do not need to worry about the exact order as long as you leave it to increase every time. If the rest of your code uses memory order, you should use this. If your code uses any other convention completely, you also need to apply it here. Under no circumstances should you confuse agreements within any set of locks that can be stored at the same time, which simply causes problems.
To answer a question in a comment:
A custom lock lock scheme can be useful in certain circumstances, especially if you need to hold some locks (A) for a long time, but some (B) only for a while while holding a long one. For example, if you need to run long tasks for objects of type A that briefly affect many instances of B. The agreement will be to always get a lock for A first, and then lock the object B:
void doStuff (A & a, std :: list <B *> bs)
{
boost :: unique_lock <boost :: mutex> la (a.mutex); // lock a throughout
for (std :: list <B *> :: iterator ib = bs.begin (); ib! = bs.end (); ++ ib)
{
// lock each B only for one loop iteration
boost :: unique_lock <boost :: mutex> lb (ib-> mutex);
// work on a and * ib
// ...
}
}
You can refuse to lock A between each iteration of the loop and use the Boost / C ++ 0x lock order, but depending on what doStuff () does, this can make the algorithm more complicated or confusing.
Another example: In runtime environments where objects do not necessarily remain in the same memory location (for example, due to copying the garbage collection), relying on the memory address for ordering will not be reliable. Thus, you can give each object a unique identifier and create a blocking order in the order of the identifier.