2017-06-29 11 views
1

ich mit dem Multi-Threading-Projekt mit C++ zu tun habe und ich bezweifle über std :: MutexVermeiden Race-Bedingung mit std :: Mutex

Nehmen wir an, dass ich einen Stapel haben.

Jemand sagte, dass die Verwendung dieses Stapels Race Condition vermeiden kann. Aber ich denke, dass das Problem hier ist, dass Mutex aka gegenseitigen Ausschluss hier nur für die einzelne Funktion nicht zusammen sicherstellen. Zum Beispiel kann ich die Threads Push und Pop nennen lassen. Diese Funktion hat immer noch Probleme mit dem Race-Zustand.

Zum Beispiel:

threadsafe_stack st; //global varibale for simple 

void fun1(threadsafe_stack st) 
{ 

    std::lock_guard<std::mutex> lock(m); 
    st.push(t); 
    t = st.pop(); 
    // 
} 

void fun2(threadsafe_stack st) 
{ 
    std::lock_guard<std::mutex> lock(m); 
    T t,t2; 
    t = st.pop(); 
    // Do big things 
    st.push(t2); 

    // 
} 

Wenn ein Thread fun1 und fun2 den gleichen Stapel (globale Variable für einfache) zu nennen. Also kann es eine Race Condition sein (?)

Ich habe nur eine Lösung, die ich denke, ist mit einer Art von atomaren Transaktion bedeutet statt direkt aufrufen push(), pop(), leer(), rufe ich sie über eine Funktion mit einem "Funktionszeiger" auf diese Funktion und mit nur einem Mutex.

Zum Beispiel:

#define PUSH 0 
#define POP  1 
#define EMPTY 2 

changeStack(int kindOfFunction, T* input, bool* isEmpty) 
{ 
    std::lock_guard<std::mutex> lock(m); 
    switch(kindOfFunction){ 
     case PUSH: 
      push(input); 
      break; 
     case POP: 
      input = pop(); 
      break; 
     case EMPTY: 
      isEmpty = empty(); 
      break;   
    } 
} 

Ist meine Lösung gut? Oder ich überdenke nur und die erste Lösung, die mein Freund mir gesagt hat, ist gut genug? Gibt es dafür eine andere Lösung? Die Lösung kann "atomare Transaktionen" vermeiden, wie ich es vorschlage.

+1

Warum denken Sie, dass, wenn Sie Pop/Push von Threads aufrufen, es wird eine Race Condition geben? Thread A ruft Push- und Block-Mutex auf, Thread B ruft Pop auf und wartet, bis Mutex durch Push entsperrt wird. – ForEveR

+0

Sie versuchen, ein Nicht-Problem zu lösen."Mutex aka wechselseitiger Ausschluss hier sorgt nur für die individuelle Funktion nicht zusammen." --- Das ist die fehlerhafte Annahme. –

Antwort

1

Sie missverstehen etwas. Sie brauchen diese changeStack Funktion nicht.

Wenn Sie lock_guard vergessen, hier ist, wie es aussieht (mit lock_guard, der Code das gleiche tut, aber lock_guard macht es bequem: macht entriegeln automatisch):

push() { 
    m.lock(); 
    // do the push 
    m.unlock(); 
} 

pop() { 
    m.lock(); 
    // do the pop 
    m.unlock(); 
} 

Wenn push genannt wird, Mutex wird gesperrt sein. Nun stell dir vor, dass auf anderen Threads pop genannt wird. pop versucht den Mutex zu sperren, aber er kann ihn nicht sperren, weil push ihn bereits gesperrt hat. Es muss also auf push warten, um den Mutex zu entsperren. Wenn push den Mutex entsperrt, kann pop es sperren.

Also, kurz gesagt, es ist std::mutex, die den gegenseitigen Ausschluss macht, nicht die lock_guard.

2

Ein Mutex ist eine einzelne Sperre und kann gleichzeitig von einem einzelnen Thread gehalten werden. Es funktioniert nicht nach Funktion.

Wenn also ein Thread (T1) die Sperre für ein bestimmtes Objekt in push() hält, kann ein anderer Thread (T2) ihn nicht in pop() erfassen und wird blockiert, bis T1 ihn freigibt. An diesem Punkt der Freigabe wird T2 (oder ein anderer Thread, der ebenfalls von demselben Mutex blockiert wird) entsperrt und darf fortfahren.

Sie müssen nicht alle Sperren und Entriegelungen in einem Element durchführen.

Der Punkt, an dem immer noch eine race condition Einführung werden kann, ist wie diese Konstrukte, wenn sie in der Unterhaltungs Code erscheinen:

if(!stack.empty()){ 
    auto item=stack.pop();//Guaranteed? 
} 

Wenn ein anderer Thread T2 pop() eintritt, nachdem T1 Gewinde empty() eintritt (siehe oben) und wird geblockt und wartet auf den Mutex, dann kann die pop() in T1 fehlschlagen, weil T2 'zuerst da ist'. Zwischen dem Ende von empty() und dem Anfang von pop() in diesem Ausschnitt kann eine beliebige Anzahl von Aktionen stattfinden, es sei denn, eine andere Synchronisation verarbeitet sie.

In diesem Fall sollten Sie T1 & T2 vorstellen buchstäblich-pop() Rennen aber natürlich können sie zu verschiedenen Mitgliedern Rennen und entkräften einander immer noch ...

Wenn Sie den Code bauen möchten, dass Sie normalerweise müssen dann weitere atomare Elementfunktionen wie try_pop() hinzufügen, die (sagen wir) eine leere std::shared_ptr<> zurückgibt, wenn der Stapel leer ist.

ich diesen Satz hoffen, ist nicht verwirrend:

Sperren das Objekt Mutex innerhalb Mitgliederfunktionen Rennen Bedingungen zwischen Anrufe an diese Mitgliederfunktionen aber nicht in zwischen Anrufe an diese Mitgliederfunktionen vermeidet .

Der beste Weg, dies zu lösen, ist das Hinzufügen von "Composite" -Funktionen, die mehr als eine "logische" Operation ausführen. Das neigt dazu, gegen gutes Klassen-Design zu gehen, in dem Sie eine logische Menge minimaler Operationen entwerfen und der konsumierende Code sie kombiniert.

Die Alternative besteht darin, dem Benutzer den Zugriff auf den Mutex zu erlauben. Zum Beispiel expose void lock() const; und void unlock() cont; Mitglieder. Das ist in der Regel nicht bevorzugt, da (a) ist es sehr einfach wird für Verbraucher Code zu Deadlocks erstellen und (b) Sie entweder eine rekursive Verriegelung (mit Kopf) oder wieder Member-Funktionen verdoppeln:

void pop(); //Self locking version... 
void pop_prelocked(); //Caller must hold object mutex or program invalidated. 

Ob setzen sie als public oder protected oder nicht, dass try_pop() in etwa so aussehen würde:

std::shared_ptr<T> try_pop(){ 
    std::lock_guard<std::mutex> guard(m); 
    if(empty_prelocked()){ 
     return std::shared_ptr<T>(); 
    } 
    return pop_prelocked(); 
} 

Hinzufügen eines Mutex und es am Anfang jedes Mitglieds zu erwerben ist nur der Anfang der Geschichte ...

Fußnote: Hoffentlich erklärt das mu tual ex lusion (mut **** ex). Es gibt ein ganz anderes Thema rund um Mitglieder Hindernisse hier unter der Oberfläche lauern, aber wenn Sie Mutexe auf diese Weise verwenden, können Sie dies als eine Implementierung Detail für jetzt behandeln ...

+1

Danke. Ich verstehe jetzt besser. –

Verwandte Themen