Forum: Compiler & IDEs Verschachtelte Locks / rekusive Mutexes?


von Werner (Gast)


Lesenswert?

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

von Peter II (Gast)


Lesenswert?

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.

von Kaj (Gast)


Lesenswert?

Peter II schrieb:
> bei Windows

Werner schrieb:
> aber die gibt es auf dem
> System nicht (sehr kleines Linux).

von The D. (thedaz)


Lesenswert?

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, ...)

von Peter II (Gast)


Lesenswert?

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.

von Werner (Gast)


Lesenswert?

>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?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

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.

von Schaukasten (Gast)


Lesenswert?

Andreas S. schrieb:
> Wenn man mehrere Mutexe bzw. Semaphore verwendet

Worin besteht denn der Unterschied zw. Mutexen und Semaphoren?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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).

von Sebastian (Gast)


Lesenswert?

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.

von SelbstbeweihräuchererUndTroll (Gast)


Lesenswert?

Hi Werner,

für sowas kannst einen Watchdog nehmen, der schaltet die Liste mit 
Ampeln wieder flott,,,

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.