I made a copy-paste error copying the ModuleList trick for avoiding priority inversion in taking the lhs & rhs mutexes. Pavel pointed out you can use the std::lock(lock1, lock2) form to do this more cleanly. I fixed the one I got wrong and I think I found all the other places doing this (or just locking both mutexes) and replaced them with this form.
This looks great. Thank you.
As of c++17 there will be an RAII way to do this https://en.cppreference.com/w/cpp/thread/scoped_lock. Theoretically we could jump ahead, and implement something like that ourselves, but there aren't too many uses of this pattern now, so it's not really necessary at this point.