Sí, las jerarquías de bloqueo pueden prevenir bloqueos de manera efectiva; por supuesto, si realmente puede definir una jerarquía para su programa (especialmente, en presencia de complementos) es otro asunto completamente diferente.
Los bloques básicos son simples:
- Cada mutex debe tener un nivel (ya sea determinado en tiempo de compilación o en tiempo de ejecución)
- Cada hilo debe solamente siempre adquirir mutex en orden ascendente o nivel descendente (decidir una vez)
Espero que pueda hacer justicia a la idea, por favor considere la implementación de ejemplo debajo de un boceto; nunca ha sido compilado/probado.
un mutex básica:
template <typename Mutex, size_t Level>
class HierarchicalMutex {
public:
friend class LevelManager;
void lock() {
LevelManager::Lock(*this);
}
void unlock() {
LevelManager::Unlock(*this);
}
private:
size_t previous;
Mutex mutex;
}; // class HierarchicalMutex
template <typename Mutex, size_t Level>
size_t level(HierarchicalMutex<Mutex,Level> const&) { return Level; }
papel La LevelManager
's es simplemente para asegurar que las transiciones de nivel suceden en el orden correcto.
class LevelManager {
public:
//
// Single Mutex locking
//
template <typename M>
static void Lock(M& m) {
m.previous = LevelUp(level(m));
m.mutex.lock();
}
template <typename M>
static void Unlock(M& m) {
m.mutex.unlock();
LevelDown(level(m), m.previous);
}
//
// Multiple Mutexes Group Locking
//
// Note: those should expose a "size_t level(M const&)" function,
// and calls to lock/unlock should appropriately call
// this manager to raise/lower the current level.
//
// Note: mutexes acquired as a group
// should be released with the same group.
//
template <typename M>
static void Lock(std::array_ref<M*> mutexes) { // I wish this type existed
using std::begin; using std::end;
auto begin = begin(mutexes);
auto end = end(mutexes);
end = std::remove_if(begin, end, [](M const* m) { return m == 0; });
if (begin == end) { return; }
Sort(begin, end);
size_t const previous = LevelUp(level(*std::prev(end)));
for (; begin != end; ++begin) {
begin->previous = previous;
begin->mutex.lock();
}
}
template <typename M>
static void Unlock(std::array_ref<M*> mutexes) {
using std::begin; using std::end;
auto begin = begin(mutexes);
auto end = end(mutexes);
end = std::remove_if(begin, end, [](M const* m) { return m == 0; });
if (begin == end) { return; }
Sort(begin, end);
std::reverse(begin, end);
for (auto it = begin; it != end; ++it) { it->mutex.unlock(); }
LevelDown(level(*begin), begin->previous);
}
private:
static __thread size_t CurrentLevel = 0;
template <typename It>
static void Sort(It begin, It end) {
using Ref = typename std::iterator_traits<It>::const_reference;
auto const sorter = [](Ref left, Ref right) {
return std::tie(level(left), left) < std::tie(level(right), right);
};
std::sort(begin, end, sorter);
}
static size_t LevelUp(size_t const to) {
if (CurrentLevel >= to) { throw LockHierarchyViolation(); }
CurrentLevel = to;
}
static void LevelDown(size_t const from, size_t const to) {
if (CurrentLevel != from) { throw LockHierarchyViolation(); }
CurrentLevel = to;
}
}; // class LevelManager
por diversión, he implementado la posibilidad de bloquear múltiples cerraduras del mismo nivel en un solo tiro.
Como dije en mi respuesta, no hay una clave para evitar estancamiento y hay una simple vendaje cuando has lo tengo. Es mucho mejor evitarlo en el proceso de diseño. ¿Cuál es tu caso particular? –
@ Platinum Azure: estoy buscando soluciones para problemas de interbloqueo en una base de código antigua y grande. – StackedCrooked
No tengo nada, lo siento. :-( –