Ja, Sperrhierarchien können Deadlocks wirksam verhindern; ob Sie tatsächlich eine Hierarchie für Ihr Programm definieren können (insbesondere bei Vorhandensein von Plugins), ist natürlich eine ganz andere Frage.
Die Grundbausteine sind einfach:
- Jede Mutex sollte einen Level haben (entweder zur Kompilier- oder zur Laufzeit bestimmt)
- Jeder Thread sollte immer nur einen Mutex in aufsteigender oder absteigender Reihenfolge erwerben (einmalige Entscheidung)
Ich hoffe, ich kann der Idee gerecht werden. Bitte betrachten Sie die Beispielimplementierung unten als Skizze; sie wurde nie kompiliert/getestet.
Ein einfacher Mutex:
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; }
Le site LevelManager
hat lediglich die Aufgabe, dafür zu sorgen, dass die Ebenenübergänge in der richtigen Reihenfolge erfolgen.
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
Zum Spaß habe ich die Möglichkeit implementiert, mehrere Schlösser der gleichen Stufe mit einem einzigen Schuss zu sperren.
2 Stimmen
Wie ich bereits in meiner Antwort sagte, gibt es keinen einzigen Schlüssel zur Vermeidung von Blockaden und kein einfaches Pflaster, wenn man sie hat. Es ist viel besser, sie bereits im Entwurfsprozess zu vermeiden. Was ist Ihr besonderer Fall?
0 Stimmen
@Platinum Azure: Ich bin auf der Suche nach Lösungen für Deadlock-Probleme in einer alten und großen Codebasis.
1 Stimmen
Dann habe ich nichts, tut mir leid :-(
1 Stimmen
Haben Sie die
boost::recursive_mutex
?1 Stimmen
@Alexandre recursive_mutex verhindert nur ein Self-Deadlock eines Threads, der dieselbe Mutex zweimal sperrt. Es hilft nicht, wenn zwei Threads zwei Mutexe in entgegengesetzter Reihenfolge sperren.