Ich bin neu im Multithreading und habe versucht zu verstehen, wie Mutexe funktionieren. Ich habe viel gegoogelt und ein anständiges Tutorial gefunden , aber es hat immer noch Zweifel daran gelassen, wie es funktioniert, weil ich mein eigenes Programm erstellt habe, in dem das Sperren nicht funktioniert hat.
Eine absolut nicht intuitive Syntax des Mutex ist pthread_mutex_lock( &mutex1 );
, wo es so aussieht, als würde der Mutex gesperrt, wenn ich wirklich eine andere Variable sperren möchte. Bedeutet diese Syntax, dass das Sperren eines Mutex einen Codebereich sperrt, bis der Mutex entsperrt wird? Woher wissen Threads dann, dass die Region gesperrt ist? [ UPDATE: Threads wissen, dass die Region durch Memory Fencing gesperrt ist ]. Und soll ein solches Phänomen nicht als kritischer Abschnitt bezeichnet werden? [ UPDATE: Objekte mit kritischen Abschnitten sind nur in Windows verfügbar, wo die Objekte schneller als Mutexe sind und nur für den Thread sichtbar sind, der sie implementiert. Andernfalls bezieht sich der kritische Abschnitt nur auf den durch einen Mutex geschützten Codebereich. ]
Kurz gesagt, könnten Sie bitte mit einem möglichst einfachen Mutex- Beispielprogramm und einer möglichst einfachen Erklärung der Funktionsweise helfen ? Ich bin sicher, das wird vielen anderen Neulingen helfen .
Antworten:
Hier ist mein bescheidener Versuch, Neulingen auf der ganzen Welt das Konzept zu erklären: (eine farbcodierte Version auch in meinem Blog)
Viele Leute rennen zu einer einzelnen Telefonzelle (sie haben keine Handys), um mit ihren Lieben zu sprechen. Die erste Person, die den Türgriff der Kabine erfasst, ist die Person, die das Telefon benutzen darf. Er muss sich so lange am Türgriff festhalten, wie er das Telefon benutzt, sonst ergreift jemand anderes den Griff, wirft ihn raus und spricht mit seiner Frau :) Es gibt kein Warteschlangensystem als solches. Wenn die Person ihren Anruf beendet, aus der Kabine kommt und den Türgriff verlässt, darf die nächste Person, die den Türgriff ergreift, das Telefon benutzen.
Ein Thread ist: Jede Person
Der Mutex ist: Der Türgriff
Das Schloss ist: Die Hand der Person
Die Ressource ist: Das Telefon
Jeder Thread, der einige Codezeilen ausführen muss, die nicht gleichzeitig von anderen Threads geändert werden sollten (mit dem Telefon, um mit seiner Frau zu sprechen), muss zuerst ein Schloss für einen Mutex erwerben (den Türgriff der Kabine umklammern) ). Nur dann kann ein Thread diese Codezeilen ausführen (Telefonieren).
Sobald der Thread diesen Code ausgeführt hat, sollte er die Sperre für den Mutex aufheben, damit ein anderer Thread eine Sperre für den Mutex erhalten kann (andere Personen können auf die Telefonzelle zugreifen).
[ Das Konzept, einen Mutex zu haben, ist etwas absurd, wenn man den exklusiven Zugriff in der realen Welt betrachtet, aber in der Programmierwelt gab es wohl keine andere Möglichkeit, die anderen Threads "sehen" zu lassen, dass ein Thread bereits einige Codezeilen ausführte. Es gibt Konzepte für rekursive Mutexe usw., aber dieses Beispiel sollte Ihnen nur das Grundkonzept zeigen. Ich hoffe, das Beispiel gibt Ihnen ein klares Bild des Konzepts. ]]
Mit C ++ 11-Threading:
Kompilieren und ausführen mit
g++ -std=c++0x -pthread -o thread thread.cpp;./thread
Anstatt explizit
lock
und zu verwendenunlock
, können Sie die hier gezeigten Klammern verwenden , wenn Sie eine Sperre mit Gültigkeitsbereich für den Vorteil verwenden, den sie bietet . Sperren mit Gültigkeitsbereich haben jedoch einen geringen Leistungsaufwand.quelle
(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer
. Die Verwendung der Sperrung mit Gültigkeitsbereich liegt beim Entwickler, je nachdem, welche Art von Anwendung er erstellt. Diese Antwort sollte das grundlegende Verständnis des Mutex-Konzepts ansprechen und nicht auf die Komplexität des Mutex-Konzepts eingehen. Ihre Kommentare und Links sind daher willkommen, liegen jedoch etwas außerhalb des Rahmens dieses Tutorials.Während ein Mutex verwendet werden kann, um andere Probleme zu lösen, besteht der Hauptgrund dafür, dass sie sich gegenseitig ausschließen und dadurch eine sogenannte Race-Bedingung lösen. Wenn zwei (oder mehr) Threads oder Prozesse gleichzeitig versuchen, auf dieselbe Variable zuzugreifen, besteht die Möglichkeit einer Race-Bedingung. Betrachten Sie den folgenden Code
Die Interna dieser Funktion sehen so einfach aus. Es ist nur eine Aussage. Ein typisches Pseudo-Assembler-Äquivalent könnte jedoch sein:
Da alle äquivalenten Anweisungen in Assemblersprache erforderlich sind, um die Inkrementierungsoperation für i auszuführen, sagen wir, dass das Inkrementieren von i eine nicht atmosphärische Operation ist. Eine atomare Operation kann auf der Hardware mit der Garantie abgeschlossen werden, dass sie nicht unterbrochen wird, sobald die Befehlsausführung begonnen hat. Das Inkrementieren von i besteht aus einer Kette von 3 atomaren Anweisungen. In einem gleichzeitigen System, in dem mehrere Threads die Funktion aufrufen, treten Probleme auf, wenn ein Thread zur falschen Zeit liest oder schreibt. Stellen Sie sich vor, wir haben zwei Threads, die gleichzeitig ausgeführt werden, und einer ruft die Funktion unmittelbar nach dem anderen auf. Nehmen wir auch an, wir haben i auf 0 initialisiert. Nehmen wir außerdem an, dass wir viele Register haben und dass die beiden Threads völlig unterschiedliche Register verwenden, sodass es nicht zu Kollisionen kommt. Der tatsächliche Zeitpunkt dieser Ereignisse kann sein:
Was passiert ist, ist, dass wir zwei Threads haben, die i gleichzeitig inkrementieren. Unsere Funktion wird zweimal aufgerufen, aber das Ergebnis stimmt nicht mit dieser Tatsache überein. Es sieht so aus, als ob die Funktion nur einmal aufgerufen wurde. Dies liegt daran, dass die Atomizität auf Maschinenebene "gebrochen" ist, was bedeutet, dass sich Threads gegenseitig unterbrechen oder zu falschen Zeiten zusammenarbeiten können.
Wir brauchen einen Mechanismus, um dies zu lösen. Wir müssen den obigen Anweisungen eine Bestellung auferlegen. Ein üblicher Mechanismus besteht darin, alle Threads außer einem zu blockieren. Pthread Mutex verwendet diesen Mechanismus.
Jeder Thread, der einige Codezeilen ausführen muss, die möglicherweise gemeinsam genutzte Werte von anderen Threads gleichzeitig unsicher ändern (über das Telefon mit seiner Frau sprechen), sollte zuerst eine Sperre für einen Mutex erhalten. Auf diese Weise muss jeder Thread, der Zugriff auf die gemeinsam genutzten Daten benötigt, die Mutex-Sperre durchlaufen. Nur dann kann ein Thread den Code ausführen. Dieser Codeabschnitt wird als kritischer Abschnitt bezeichnet.
Sobald der Thread den kritischen Abschnitt ausgeführt hat, sollte er die Sperre für den Mutex aufheben, damit ein anderer Thread eine Sperre für den Mutex erhalten kann.
Das Konzept eines Mutex scheint etwas seltsam, wenn man Menschen betrachtet, die exklusiven Zugang zu realen, physischen Objekten suchen, aber wenn wir programmieren, müssen wir absichtlich sein. Gleichzeitige Threads und Prozesse haben nicht die soziale und kulturelle Erziehung, die wir haben, daher müssen wir sie zwingen, Daten gut auszutauschen.
Wie funktioniert ein Mutex technisch gesehen? Leidet es nicht unter den gleichen Rennbedingungen, die wir zuvor erwähnt haben? Ist pthread_mutex_lock () nicht etwas komplexer als ein einfaches Inkrementieren einer Variablen?
Technisch gesehen benötigen wir Hardware-Unterstützung, um uns zu helfen. Die Hardware-Designer geben uns Maschinenanweisungen, die mehr als eine Sache tun, aber garantiert atomar sind. Ein klassisches Beispiel für eine solche Anweisung ist das Test-and-Set (TAS). Wenn wir versuchen, eine Sperre für eine Ressource zu erlangen, prüfen wir möglicherweise mithilfe des TAS, ob ein Wert im Speicher 0 ist. Wenn dies der Fall ist, ist dies unser Signal dafür, dass die Ressource verwendet wird und wir nichts (oder genauer) tun Wir warten durch einen Mechanismus. Ein Pthreads-Mutex versetzt uns in eine spezielle Warteschlange im Betriebssystem und benachrichtigt uns, wenn die Ressource verfügbar wird. Bei dümmeren Systemen müssen wir möglicherweise eine enge Schleife durchführen und den Zustand immer wieder testen. . Wenn der Wert im Speicher nicht 0 ist, setzt der TAS den Speicherort auf einen anderen Wert als 0, ohne andere Anweisungen zu verwenden. Es' Es ist so, als würde man zwei Montageanweisungen zu einer kombinieren, um Atomizität zu erhalten. Daher kann das Testen und Ändern des Werts (falls eine Änderung angemessen ist) nicht unterbrochen werden, sobald er begonnen hat. Wir können Mutexe auf einer solchen Anweisung aufbauen.
Hinweis: Einige Abschnitte ähneln möglicherweise einer früheren Antwort. Ich nahm seine Einladung zur Bearbeitung an, er bevorzugte die ursprüngliche Art und Weise, also behalte ich das, was ich hatte, was mit ein wenig seiner Redewendung durchsetzt ist.
quelle
Das beste Thread-Tutorial, das ich kenne, ist hier:
https://computing.llnl.gov/tutorials/pthreads/
Ich finde es gut, dass es sich eher um die API als um eine bestimmte Implementierung handelt und einige nette einfache Beispiele enthält, die Ihnen helfen, die Synchronisation zu verstehen.
quelle
Ich bin kürzlich auf diesen Beitrag gestoßen und denke, dass er eine aktualisierte Lösung für den c ++ 11-Mutex der Standardbibliothek benötigt (nämlich std :: mutex).
Ich habe unten einen Code eingefügt (meine ersten Schritte mit einem Mutex - ich habe die Parallelität unter win32 mit HANDLE, SetEvent, WaitForMultipleObjects usw. gelernt).
Da es mein erster Versuch mit std :: mutex und Freunden ist, würde ich gerne Kommentare, Vorschläge und Verbesserungen sehen!
quelle
Die Funktion
pthread_mutex_lock()
entweder erwirbt den Mutex für den anrufenden Thread oder blockiert den Thread , bis der Mutex erworben werden können. Der zugehörigepthread_mutex_unlock()
gibt den Mutex frei.Stellen Sie sich den Mutex als Warteschlange vor. Jeder Thread, der versucht, den Mutex abzurufen, wird am Ende der Warteschlange platziert. Wenn ein Thread den Mutex freigibt, wird der nächste Thread in der Warteschlange beendet und ausgeführt.
Ein kritischer Abschnitt bezieht sich auf einen Codebereich, in dem Nichtdeterminismus möglich ist. Dies liegt häufig daran, dass mehrere Threads versuchen, auf eine gemeinsam genutzte Variable zuzugreifen. Der kritische Abschnitt ist nicht sicher, bis eine Art Synchronisation vorhanden ist. Eine Mutex-Sperre ist eine Form der Synchronisation.
quelle
Sie sollten die Mutex-Variable überprüfen, bevor Sie den durch den Mutex geschützten Bereich verwenden. Ihr pthread_mutex_lock () könnte also (abhängig von der Implementierung) warten, bis mutex1 freigegeben wird, oder einen Wert zurückgeben, der angibt, dass die Sperre nicht erhalten werden konnte, wenn jemand anderes sie bereits gesperrt hat.
Mutex ist wirklich nur ein vereinfachtes Semaphor. Wenn Sie darüber lesen und verstehen, verstehen Sie Mutexe. Es gibt verschiedene Fragen zu Mutexen und Semaphoren in SO. Unterschied zwischen binärem Semaphor und Mutex , wann sollten wir Mutex verwenden und wann sollten wir Semaphor verwenden und so weiter. Das Toilettenbeispiel im ersten Link ist ungefähr so gut wie man es sich vorstellen kann. Der Code überprüft lediglich, ob der Schlüssel verfügbar ist, und reserviert ihn gegebenenfalls. Beachten Sie, dass Sie nicht wirklich die Toilette selbst reservieren, sondern den Schlüssel.
quelle
pthread_mutex_lock
kann nicht zurückkehren, wenn jemand anderes das Schloss hält. Es blockiert in diesem Fall und das ist der springende Punkt.pthread_mutex_trylock
ist die Funktion, die zurückgegeben wird, wenn die Sperre gehalten wird.Für diejenigen, die das Shortex-Mutex-Beispiel suchen:
quelle
SEMAPHORE BEISPIEL ::
Referenz: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
quelle