2013-04-05 3 views
5

Ich habe zwei Threads versuchen, das gleiche zu sperren boost::mutex. Einer dieser Threads verarbeitet fortlaufend Daten und der andere zeigt regelmäßig den aktuellen Status an. Der Verarbeitungsthread, entsprechend meiner Absicht, gibt das Schloss sehr häufig frei und nimmt es wieder auf, so dass der Anzeigethread es anzapfen und es bei Bedarf abrufen kann. Offensichtlich möchte ich, dass der Anzeige-Thread die Sperre annimmt, wenn er das nächste Mal vom Prozess-Thread freigegeben wird. Dies geschieht jedoch nicht, stattdessen wartet es auf die Sperre und erhält sie erst nach vielen Sperren-Freigabe-Zyklen vom Prozeß-Thread.Erwerben Sie Schloss, sobald es verfügbar ist

überprüfen Sie bitte die minimal Beispiel zeigt, mein Problem:

#include <boost/thread.hpp> 
#include <iostream> 

using namespace std; 
using namespace boost; 

mutex mut; 

void process() { 
     double start = time(0); 
     while(1) { 
       unique_lock<mutex> lock(mut); 
       this_thread::sleep(posix_time::milliseconds(10)); 
       std::cout<<"."; 
       if(time(0)>start+10) break; 
     } 
} 

int main() { 

     thread t(process); 

     while(!t.timed_join(posix_time::seconds(1))) { 
       posix_time::ptime mst1 = posix_time::microsec_clock::local_time(); 
       cout<<endl<<"attempting to lock"<<endl; 
       cout.flush(); 

       unique_lock<mutex> lock(mut); 

       posix_time::ptime mst2 = posix_time::microsec_clock::local_time(); 
       posix_time::time_duration msdiff = mst2 - mst1; 
       cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl; 
       cout.flush(); 
     } 

} 

Zusammengestellt mit: g++ mutextest.cpp -lboost_thread -pthread

Wenn ich die ausführbare Datei ausführen, ein Beispiel für die Ausgabe ist wie folgt:

................................................................................................... 
attempting to lock 
.................................................................................................................................................................................................................................................................................................................................................................................................................................... 
acquired lock in: 4243 
................................................................................................... 
attempting to lock 
........................................................................................................ 
acquired lock in: 1049 
................................................................................................... 
attempting to lock 
........................................................................................................................ 
acquired lock in: 1211 
.................................... 

Wie Sie kann sehen, im schlimmsten Fall wartet der Display-Thread auf 424 Lock-Release-Zyklen, bevor es darum geht, das Schloss zu fangen.

Ich benutze offensichtlich den Mutex auf eine falsche Art, aber was ist der übliche Weg, um das zu lösen?

+0

neben üblichen Verdächtigen: (condition variable, yield,) probiere einen thread safe container ... – NoSenseEtAl

Antwort

0

Ich habe das Problem mit den Bedingungen gelöst, wie von Dale Wilson ein FKaria vorgeschlagen, aber ich habe es in die entgegengesetzte Richtung verwendet. Der Prozess-Thread prüft also auf ein Pause-Flag, und wenn er gesetzt ist, wartet er auf eine Bedingung und gibt die Sperre frei. Der Anzeigethread steuert das Pause-Flag und setzt den Prozessthread ebenfalls fort, indem er über die Bedingung benachrichtigt wird. Code: (Der Code meist das gleiche, ich habe die neuen Linien mit //New markiert)

#include <boost/thread.hpp> 
#include <boost/thread/condition.hpp> 
#include <iostream> 

using namespace std; 
using namespace boost; 

mutex mut; 
condition cond; 

volatile bool shouldPause = false; //New 

void process() { 
     double start = time(0); 
     while(1) { 
       unique_lock<mutex> lock(mut); 

       if(shouldPause) cond.wait(mut); //New 

       this_thread::sleep(posix_time::milliseconds(10)); 
       std::cout<<"."; 
       if(time(0)>start+10) break; 
     } 
} 

int main() { 

     thread t(process); 

     while(!t.timed_join(posix_time::seconds(1))) { 
       posix_time::ptime mst1 = posix_time::microsec_clock::local_time(); 
       cout<<endl<<"attempting to lock"<<endl; 
       cout.flush(); 
       shouldPause = true; // New 
       unique_lock<mutex> lock(mut); 

       posix_time::ptime mst2 = posix_time::microsec_clock::local_time(); 
       posix_time::time_duration msdiff = mst2 - mst1; 
       cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl; 
       cout.flush(); 

       shouldPause = false; // New 
       cond.notify_all(); // New 
     } 

} 

Jetzt ist die Ausgabe genau, wie ich es sein möchte:

................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 8 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 8 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 8 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
.......................... 
4

Es ist nicht so, dass Sie den Mutex falsch verwenden, nur das Threading tut nicht, was Sie hier erwarten. Das Betriebssystem entscheidet, welcher Thread wann ausgeführt wird (dies wird als "Planung" bezeichnet), und es gibt nichts im Code, das einen Threadwechsel am Ende der Schleife erzwingt; Der Thread läuft weiter und erwirbt die Sperre erneut. Eine Sache zu versuchen ist, einen Anruf zu this_thread::yield() nach der Freigabe der Sperre hinzufügen (in diesem Fall, an der Spitze der Schleife, vor dem Wiederverschließen); Das wird dem Scheduler vorschlagen, dass es für einen anderen Thread geeignet ist. Wenn Sie eng synchronisiertes Interleaving wirklich wollen, werden Threads es nicht tun; Schreiben Sie stattdessen eine Funktion auf höherer Ebene, die Ihre beiden Funktionen nacheinander aufruft.

+0

'yield()' schien etwas zu helfen, aber es ist immer noch schlecht. Mit 'yield()' vor der 'unique_lock'-Konstruktion im Verarbeitungsthread werden immer noch bis zu ~ 300 Sperrenfreigabe-Iterationen angezeigt, bevor der Anzeigethread die Sperre auffängt. Ich möchte nicht wirklich tight interleaving, da es nur ein Display ist, aber ich würde mich freuen, wenn es nicht auf 300 freaking Iterationen warten würde :) – enobayram

+0

Yuk. Versuche, 'sleep' zu nennen; Wenn ein Thread schläft, führt jedes vernünftige Betriebssystem einen anderen Thread aus. –

+0

Ich habe das auch versucht, wenn Sie für 0 Millisekunden schlafen, dann ist es, als ob der Schlaf nicht existiert, sonst können Sie mindestens für 10 Millisekunden schlafen und das ist einfach zu viel für eine solche Störung zu verschwenden:/Ich meine Es ist nicht so, als würde ich nach einer theoretischen Unmöglichkeit fragen, die Sperre, die die Implementierung gewährt, muss sie einfach dem ersten Kandidaten geben. – enobayram

1

Wenn der Update-Thread nichts anderes zu tun hat, kann er warten, bis der Mutex verfügbar wird.

Schauen Sie sich boost :: condition_variable an. Sie können darüber hier http://www.boost.org/doc/libs/1_53_0/doc/html/thread/synchronization.html und hier Using boost condition variables

lesen Sie die Update-Thread schlafen gehen Wenn mit wäre ein Problem - es auf vielen GUI-Systeme ist und Sie nicht angeben, welche Sie verwenden - erwägen, eine Nachricht vom Verarbeitungsthread an den Aktualisierungs-Thread zu senden. (Die Details hängen wiederum von Ihrer Plattform ab.) Die Nachricht kann entweder die Informationen enthalten, die zum Aktualisieren der Anzeige erforderlich sind, oder eine Benachrichtigung sein, dass "jetzt ein guter Zeitpunkt zum Suchen" wäre.

Wenn Sie einen Bedingungscode verwenden, sollte der Verarbeitungsthread wahrscheinlich nach dem Signalisieren der Bedingung und vor dem erneuten Erwerb der Sperre nachgeben, um ein großes Fenster für den Aktualisierungs-Thread zu öffnen.

+0

Beachten Sie, dass mein Update-Thread bereits darauf wartet, dass der Mutex verfügbar wird, aber das Problem ist, dass er die Sperre nicht für sehr lange Zeit annimmt, selbst nachdem er verfügbar ist. Wenn das GUI-System wirklich wichtig ist, verwendet mein Display-Thread OpenGL, um einige Bildschirmzeichnungen durchzuführen. – enobayram

+0

Der Verarbeitungsthread, der eine Nachricht an den Update-Thread schreibt, wird nicht wirklich helfen. Der Display-Thread kann nicht "schauen", ohne die Sperre zu erhalten, und genau dort liegt das Problem. – enobayram

1

Sie können sich gerne boost::condition_variable ansehen, speziell bei den Methoden wait() und notify_one() oder notify_all(). Die Methode wait() blockiert den aktuellen Thread, bis die Sperre aufgehoben wird. Die Methoden notify_one() und notify_all() benachrichtigen einen oder alle Threads, die auf die Ausführung der Sperre warten.

+0

Ich habe bereits versucht, die 'condition_variable', aber es funktioniert nicht. 'notify_x'-Methoden bringen andere Threads in einen" Bereit "-Zustand, aber sie erzwingen kein Aufwachen. In jedem Fall brauchen Sie das Schloss an erster Stelle, bevor Sie darauf warten, mein Problem ist nicht in der Lage, das Schloss zu erwerben. – enobayram

1

Wie ich es sehe, ist das Prinzip der Mutexe nicht Fairness, sondern Korrektheit. Mutexe selbst können den Scheduler nicht steuern.Eine Sache, die mich stört, ist der Grund, warum Sie gewählt haben, 2 Threads zu erstellen, die eine solche grobkörnige Sperre verwenden. In der Theorie führen Sie zwei Dinge parallel, aber tatsächlich machen Sie sie interdependent/seriell.

Pete Idee scheint viel schöner (eine Funktion läuft diese zeichnen & update) und Sie können immer noch Threads innerhalb jeder der inneren Funktionen, nicht so viel Streit und Fairness so viel zu kümmern.

Wenn Sie wirklich wollen, zwei Threads parallel laufen lassen, dann kann ich Ihnen nur ein paar Tipps geben:

  • spinlock mit atomics und vergessen Sie Mutex,
  • gehen in die Implementierung definiert Land und Setzen Sie die Thread-Prioritäten, oder
  • machen die Verriegelung viel feinkörniger.

Keines von diesen ist unglücklicherweise unglücklich.

+0

Danke für deine Vorschläge, sie haben alle nützliche Fälle, aber nicht meine: Ein Spinlock ist einfach zu verschwenderisch, ich möchte tragbar bleiben und die Verriegelung ist so feinkörnig, wie es theoretisch sein könnte. – enobayram

+0

Wie Sie sie in einer einzigen Funktion verbinden; Ich kann sehen, dass es ein guter Ansatz für das minimale Beispiel wäre, aber die reale Anwendung ist viel komplizierter. Außerdem mache ich auch andere Dinge im Display-Thread. Ich extrahiere die notwendigen Informationen aus dem Weltmodell und lasse die Sperre los, während der Anzeige-Thread die OpenGL-Aufrufe handhabt. – enobayram

+0

Schließlich verwende ich auch das Multiple-Threads-Modell als eine Art der Abstraktion, es vereinfacht mein Design wirklich. – enobayram