Hallo,
ich habe eine Datenklasse, die zahlreiche Setter- und Gettermethoden
hat, die aus verschiedenen Threads aufgerufen werden. In allen Setter-
und Gettern wird mit einem Mutex gesperrt. Manche Setter rufen intern
andere Setter auf (die aber auch direkt zur Verfügung stehen sollen), so
dass manchmal in dem Geschachtel Deadlocks entstehen und nichts mehr
geht. Ich würde rekursive Mutexes nehmen, aber die gibt es auf dem
System nicht (sehr kleines Linux). Manche sagen ja sogar dass rekursive
Mutexes ein schlechtes Design sind. Welche Möglichkeiten gäbe es sonst
noch?
Werner
Werner schrieb:> Welche Möglichkeiten gäbe es sonst> noch?
bei Windows kann man mit Critital Section arbeiten, die blockiert sich
im gleichen Thread nicht, ist sogar schneller als ein Mutex.
Sonst ist es meist keine gute Idee ein Objekt innerhalb von sich selber
Threadsave zu machen, das wird üblicherweise außerhalb gemacht.
Wenn man nur mit einen Thread darauf zugreift, kosten die sperren nur
sinnlos Ressourcen. Nur der externe kann wissen wie er das Objekt
verwendet.
Werner schrieb:> ... Ich würde rekursive Mutexes nehmen, aber die gibt es auf dem> System nicht (sehr kleines Linux). Manche sagen ja sogar dass rekursive> Mutexes ein schlechtes Design sind. Welche Möglichkeiten gäbe es sonst> noch?>> Werner
Hast du keine Möglichkeit rekursive Mutexe in deinem Linux nachzurüsten
? Critical sections haben leider den Nachteil, das alle Prozesse beim
betreten der Section angehalten werden und nicht nur die, die die
Section ebenfalls betreten wollen.
Ob eine rekursive Mutex ein schlechtes Design darstellt ist eine Frage
des spezifischen Anwendungsfalls und kann nicht pauschal beantwortet
ist. Generell gilt, wenn es ein besseres Design zur Lösung eines
Problems gibt, dann sollte man es auch nehmen. Und was besser ist, hängt
von den individuellen Randbedingungen und den Prioritäten ab (Zeit,
Geld, Wartbarkeit, Testbarkeit, ...)
The D. schrieb:> Critical sections haben leider den Nachteil, das alle Prozesse beim> betreten der Section angehalten werden und nicht nur die, die die> Section ebenfalls betreten wollen.
nein, wie soll denn den Prozessen anhalten? Es blockiert doch nur der
Aufruf "EnterCrititalSection", wenn jemand anders schon drin ist.
Der Ansatz von Werner ist schon falsch, die sperre sollte vom Aufrufer
erfolgen schon ist sein Problem komplett verschwunden.
>Nur der externe kann wissen wie er das Objekt>verwendet.
...und deswegen brauche ich doch eigentlich eine "objektglobale" Sperre,
also einen Mutex der IN meinem Objekt sitzt?
Hallo Werner,
Werner schrieb:> Manche Setter rufen intern> andere Setter auf (die aber auch direkt zur Verfügung stehen sollen), so> dass manchmal in dem Geschachtel Deadlocks entstehen und nichts mehr> geht.
Da hier nur ein mutex im Spiel zu sein scheint, kann das kein deadlock
sein. Dein Problem ist ein double lock. So etwas lässt sich extrem
einfach vermeiden:
Der mutex wird immer nur in einer öffentlichen Funktion gelocked.
Öffentliche Funktionen werden intern nicht aufgerufen. Für den Fall,
dass eine öffentliche Funktion intern verwendet werden muss, wird sie
aufgeteilt in einen öffentlichen Teil, der nur den mutex locked und eine
Implementierung, die nur mit gelocktem mutex aufgerufen wird. Beispiel:
1
class foo {
2
public:
3
int a() const
4
{
5
std::lock_guard<std::mutex> lock( mutex_ );
6
7
return a_ + b_impl();
8
}
9
10
int b() const
11
{
12
std::lock_guard<std::mutex> lock( mutex_ );
13
14
return b_impl();
15
}
16
private:
17
int b_impl() const
18
{
19
return a_ + b_;
20
}
21
22
mutable std::mutex mutex_;
23
24
int a_;
25
int b_;
26
};
> Ich würde rekursive Mutexes nehmen, aber die gibt es auf dem> System nicht (sehr kleines Linux). Manche sagen ja sogar dass rekursive> Mutexes ein schlechtes Design sind.
recursive mutexes sind Mist, weil sich das obige Problem ganz einfach
ohne recursive mutexes lösen lässt.
Wenn ich dead locks verhindern möchte, dann brauche ich eine
Lock-Hierarchie. Daraus ergibt sich eine Reihenfolge, in der ich 2 oder
mehr mutexe locken muss, damit keine dead locks auftreten können.
Wenn ich an irgend einer Stelle in der Software nicht mehr sagen kann,
welchen mutex ich schon gelocked habe, dann kann ich auch keine dead
locks verhindern.
Ein recursive lock löst also kein Problem, ist aber teurer, da er intern
Buch über lock count und lock owner führen muss und diese Informationen
auf synchronisieren muss.
mfg Torsten
Wenn man mehrere Mutexe bzw. Semaphore verwendet, kann man Deadlocks
dadurch verhindern, dass das Belegen in allen Kontexten in derselben
Reihenfolge erfolgt und das Freigeben umgekehrt dazu. Man kann in den
Namen des Mutex z.B. einen numerischen Teil aufnehmen, um die
Angelegenheit übersichtlicher zu machen. Ebenso kann man sich ggf.
Wrapper basteln, in denen mit Hilfe von Assertions eine falsche
Reihenfolge beim Belegen oder Freigeben erkannt und beanstandet wird.
Schaukasten schrieb:> Andreas S. schrieb:>> Wenn man mehrere Mutexe bzw. Semaphore verwendet>> Worin besteht denn der Unterschied zw. Mutexen und Semaphoren?
Ein Mutex ist eine binäre Semaphore, bei der noch zusätzlich die Regel
gilt, dass die Inkrement- / Dekrement-Operationen Paarweise vom selben
thread / process aufgerufen werden (der thread, der den mutex locked,
unlocked ihn auch wieder).
Werner schrieb:> Manche sagen ja sogar dass rekursive Mutexes ein schlechtes Design sind.
Ja, sie sind ein Hinweis auf schlechtes Design. Der Grund: Mit einem
Mutex willst du eine Invariante schützen.
Beispiel: Funktion A ruft Funktion B auf, beide sperren den Mutex.
(Bei dir entsprechen die getter/setter also Funktion B).
Wenn Funktion B die Invariante schon erhält, braucht A den Mutex nicht
mehr zu sperren. Wenn sie es nicht tut, sollte sie nicht in der
öffentlichen Schnittstelle sein. Wenn A manchmal über getter/setter auf
interne Zustände zugreift, und manchmal direkt, hast du verschiedene
Abstraktionsebenen darin.
Wie du das Design schöner machen kannst, hat Torsten Robitzki schon
geschrieben.
> Welche Möglichkeiten gäbe es sonst noch?
Du kannst die rekursiven Mutexes selbst programmieren, mit Hilfe von
normalen Mutexes und einem Zähler. Falls du bei den normalen Mutexes
rausfinden kannst, ob der Besitzer die gerade laufende Task ist.
Sonst müsstest du dir in deinem rekursiven Mutex noch merken, welche
Task der Besitzer ist. Dazu musst du nur irgendwie die Task-ID
rausfinden können.