it-swarm.com.de

Mutex-Beispiel/Tutorial?

Ich bin neu im Multithreading und habe versucht zu verstehen, wie Mutexes funktionieren. Habe viel gegoogelt und ich habe ein anständiges Tutorial gefunden , aber es hat immer noch Zweifel darüber gelassen, wie es funktioniert, weil ich mein eigenes Programm erstellt habe, bei dem das Sperren nicht funktionierte.

Eine absolut nicht intuitive Syntax des Mutex ist pthread_mutex_lock( &mutex1 );, wobei es so aussieht, als würde der Mutex gesperrt, und was ich wirklich sperren möchte, ist eine andere Variable. Bedeutet diese Syntax, dass das Sperren eines Mutex einen Code-Bereich sperrt, bis der Mutex entsperrt ist? Woher wissen dann Threads, dass die Region gesperrt ist? [UPDATE: Threads wissen, dass die Region durchMemory Fencing ] gesperrt ist. Und sollte ein solches Phänomen nicht als kritischer Abschnitt bezeichnet werden? [UPDATE: Kritische Abschnittsobjekte sind nur in Windows verfügbar. Dort sind die Objekte schneller als Mutexe und nur für den Thread sichtbar, der sie implementiert. Ansonsten bezieht sich der kritische Abschnitt nur auf den durch einen Mutex geschützten Code-Bereich].

Kurz gesagt, könnten Sie bitte mit dem einfachsten möglichen Mutex Beispielprogramm und mit dem einfachsten möglichen Erklärung der Logik, wie es funktioniert, helfen? Ich bin mir sicher, dass dies viel anderer Neulinge helfen wird.

151
Nav

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 Telefonzelle (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 an der Türklinke festhalten, wie er telefoniert, sonst ergreift jemand 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 lautet: 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 sollen (über das Telefon mit seiner Frau sprechen), muss zuerst eine Sperre für einen Mutex erwerben (den Türgriff der Kabine umklammern) ). Nur dann kann ein Thread diese Codezeilen ausführen (den Anruf tätigen).

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 erlangen kann (andere Personen können auf die Telefonzelle zugreifen).

[ Das Konzept, einen Mutex zu haben, ist ein bisschen absurd, wenn man den exklusiven Zugriff in der realen Welt betrachtet, aber in der Programmwelt gab es wohl keine andere Möglichkeit, die anderen Threads "sehen" zu lassen, dass ein Thread war Einige Codezeilen werden bereits ausgeführt. Es gibt Konzepte für rekursive Mutexe usw. Dieses Beispiel sollte Ihnen jedoch nur das Grundkonzept zeigen. Ich hoffe, das Beispiel gibt Ihnen ein klares Bild des Konzepts. ]

Mit C++ 11-Threading:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m;//you can use std::lock_guard if you want to be exception safe
int i = 0;

void makeACallFromPhoneBooth() 
{
    m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside
      //man happily talks to his wife from now....
      std::cout << i << " Hello Wife" << std::endl;
      i++;//no other thread can access variable i until m.unlock() is called
      //...until now, with no interruption from other men
    m.unlock();//man lets go of the door handle and unlocks the door
}

int main() 
{
    //This is the main crowd of people uninterested in making a phone call

    //man1 leaves the crowd to go to the phone booth
    std::thread man1(makeACallFromPhoneBooth);
    //Although man2 appears to start second, there's a good chance he might
    //reach the phone booth before man1
    std::thread man2(makeACallFromPhoneBooth);
    //And hey, man3 also joined the race to the booth
    std::thread man3(makeACallFromPhoneBooth);

    man1.join();//man1 finished his phone call and joins the crowd
    man2.join();//man2 finished his phone call and joins the crowd
    man3.join();//man3 finished his phone call and joins the crowd
    return 0;
}

Kompilieren und ausführen mit g++ -std=c++0x -pthread -o thread thread.cpp;./thread

Anstatt lock und unlock explizit zu verwenden, können Sie Klammern wie hier gezeigt ​​verwenden, wenn Sie eine Bereichsverriegelung verwenden für den Vorteil, den sie bietet . Scoped-Sperren haben jedoch einen geringen Leistungsaufwand.

Mit TBB: Sie benötigen TBB , um das folgende Programm auszuführen, aber die Absicht, TBB-Code zu veröffentlichen, ist dies Sie verstehen die Abfolge des Sperrens und Entsperrens, indem Sie sich nur den einfachen Code ansehen (Sie hätten das Sperren des Gültigkeitsbereichs anzeigen können, wenn Sie Acquise und Release nicht verwendet hätten - was ebenfalls ausnahmesicher ist ​​-, aber das ist klarer).

#include <iostream>
#include "/tbb/mutex.h"
#include "/tbb/tbb_thread.h"
using namespace tbb;

typedef mutex myMutex;
static myMutex sm;
int i = 0;

void someFunction() 
{ 
      //Note: Since a scoped lock is used below, you should know that you 
      //can specify a scope for the mutex using curly brackets, instead of 
      //using lock.acquire() and lock.release(). The lock will automatically 
      //get released when program control goes beyond the scope.
      myMutex::scoped_lock lock;//create a lock
      lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex
         //***only one thread can access the lines from here...***
         ++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release.
         sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed
         std::cout<<"In someFunction "<<i<<"\n";
         //***...to here***
      lock.release();//releases the lock (duh!)      
}

int main()
{
   tbb_thread my_thread1(someFunction);//create a thread which executes 'someFunction'
   tbb_thread my_thread2(someFunction);
   tbb_thread my_thread3(someFunction);

   my_thread1.join();//This command causes the main thread (which is the 'calling-thread' in this case) to wait until thread1 completes its task.
   my_thread2.join();
   my_thread3.join();
}

Beachten Sie, dass tbb_thread.h veraltet ist. Der Ersatz wird angezeigt hier .

249
Nav

Während ein Mutex zur Lösung anderer Probleme verwendet werden kann, besteht der Hauptgrund dafür, dass sie sich gegenseitig ausschließen und auf diese Weise einen sogenannten Race-Zustand lösen. Wenn zwei (oder mehr) Threads oder Prozesse versuchen, gleichzeitig auf dieselbe Variable zuzugreifen, besteht die Möglichkeit einer Race-Bedingung. Betrachten Sie den folgenden Code

//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
  i++;
}

Das Innere dieser Funktion sieht so einfach aus. Es ist nur eine Aussage. Ein typisches Pseudo-Assembly-Äquivalent könnte jedoch Folgendes sein:

load i from memory into a register
add 1 to i
store i back into memory

Da alle äquivalenten Anweisungen für die Assembler-Sprache erforderlich sind, um die Inkrementierungsoperation für i auszuführen, sagen wir, dass das Inkrementieren von i eine nicht-atmoische Operation ist. Eine atomare Operation kann auf der Hardware abgeschlossen werden, wobei die Garantie besteht, 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 laufen, und einer ruft die Funktion unmittelbar nach dem anderen auf. Nehmen wir auch an, dass wir i auf 0 initialisiert haben. Nehmen wir außerdem an, dass wir viele Register haben und dass die beiden Threads völlig unterschiedliche Register verwenden, so dass es keine Kollisionen gibt. Der tatsächliche Zeitpunkt dieser Ereignisse kann sein:

thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1

Was passiert ist, ist, dass wir zwei Threads haben, die gleichzeitig inkrementiert werden, 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, dh Threads können sich gegenseitig unterbrechen oder zu falschen Zeitpunkten zusammenarbeiten.

Wir brauchen einen Mechanismus, um das zu lösen. Wir müssen den obigen Anweisungen einige Anweisungen auferlegen. Ein gängiger Mechanismus ist das Blockieren aller Threads außer einem. Pthread-Mutex verwendet diesen Mechanismus.

Jeder Thread, der einige Codezeilen ausführen muss, um gemeinsam genutzte Werte von anderen Threads gleichzeitig zu ändern (mit dem Telefon, um mit seiner Frau zu sprechen), muss 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 passieren. Nur dann kann ein Thread den Code ausführen. Dieser Codeabschnitt wird als kritischer Abschnitt bezeichnet.

Nachdem der Thread den kritischen Abschnitt ausgeführt hat, sollte er die Sperre des Mutex aufheben, damit ein anderer Thread eine Sperre des Mutex erhalten kann.

Das Konzept, einen Mutex zu haben, scheint ein wenig seltsam zu sein, wenn es um Menschen geht, die einen exklusiven Zugang zu realen, physischen Objekten suchen, aber beim Programmieren müssen wir vorsätzlich sein. Gleichzeitige Threads und Prozesse haben nicht die soziale und kulturelle Erziehung, die wir machen, daher müssen wir sie zwingen, Daten gut auszutauschen.

Technisch gesehen, wie funktioniert ein Mutex? 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 Hardwareunterstützung, um uns zu unterstützen. Die Hardware-Designer geben uns Maschinenanweisungen, die mehr als eine Sache tun, aber garantiert sind, um atomar zu sein. Ein klassisches Beispiel für eine solche Anweisung ist das Test-and-Set (TAS). Beim Versuch, eine Sperre für eine Ressource zu erhalten, verwenden wir möglicherweise das TAS, um zu prüfen, ob ein Wert im Speicher 0 ist. Wenn dies der Fall ist, wäre dies unser Signal, 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 Dumber-Systemen müssen wir eine enge Spin-Schleife ausführen, um die Bedingung immer und immer wieder zu testen. . Wenn der Wert im Speicher nicht 0 ist, setzt der TAS die Position auf einen anderen Wert als 0, ohne andere Anweisungen zu verwenden. Es ist wie das Kombinieren von zwei Montageanweisungen in 1, um uns Atomizität zu verleihen. Daher kann das Testen und Ändern des Werts (wenn das Ändern angemessen ist) nicht unterbrochen werden, sobald er begonnen hat. Wir können Mutexes auf einer solchen Anweisung aufbauen.

Hinweis: Einige Abschnitte ähneln einer früheren Antwort. Ich akzeptierte seine Einladung zur Bearbeitung, er zog es vor, die ursprüngliche Form zu bevorzugen, also bewahre ich auf, was ich hatte, was mit ein wenig von seinem Wort getränkt ist.

34
San Jacinto

Das beste Thread-Tutorial, das ich kenne, ist hier:

https://computing.llnl.gov/tutorials/pthreads/

Ich mag es, dass es über die API geschrieben wurde und nicht über eine bestimmte Implementierung. Es enthält einige einfache Beispiele, die Ihnen beim Verständnis der Synchronisation helfen.

11
R..

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 (nämlich std :: mutex) benötigt. 

Ich habe hier unten etwas Code eingefügt (meine ersten Schritte mit einem Mutex - ich habe mit win32 mit HANDLE, SetEvent, WaitForMultipleObjects usw. Parallelität gelernt).

Da es mein erster Versuch mit std :: mutex und Freunden ist, würde ich mich über Kommentare, Vorschläge und Verbesserungen freuen!

#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>


int _tmain(int argc, _TCHAR* argv[])
{   
    // these vars are shared among the following threads
    std::queue<unsigned int>    nNumbers;

    std::mutex                  mtxQueue;
    std::condition_variable     cvQueue;
    bool                        m_bQueueLocked = false;

    std::mutex                  mtxQuit;
    std::condition_variable     cvQuit;
    bool                        m_bQuit = false;


    std::thread thrQuit(
        [&]()
        {
            using namespace std;            

            this_thread::sleep_for(chrono::seconds(5));

            // set event by setting the bool variable to true
            // then notifying via the condition variable
            m_bQuit = true;
            cvQuit.notify_all();
        }
    );


    std::thread thrProducer(
        [&]()
        {
            using namespace std;

            int nNum = 13;
            unique_lock<mutex> lock( mtxQuit );

            while ( ! m_bQuit )
            {
                while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout )
                {
                    nNum = nNum + 13 / 2;

                    unique_lock<mutex> qLock(mtxQueue);
                    cout << "Produced: " << nNum << "\n";
                    nNumbers.Push( nNum );
                }
            }
        }   
    );

    std::thread thrConsumer(
        [&]()
        {
            using namespace std;
            unique_lock<mutex> lock(mtxQuit);

            while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout )
            {
                unique_lock<mutex> qLock(mtxQueue);
                if( nNumbers.size() > 0 )
                {
                    cout << "Consumed: " << nNumbers.front() << "\n";
                    nNumbers.pop();
                }               
            }
        }
    );

    thrQuit.join();
    thrProducer.join();
    thrConsumer.join();

    return 0;
}
7
fishfood

Die Funktion pthread_mutex_lock() entweder übernimmt den Mutex für den aufrufenden Thread oder blockiert den Thread, bis der Mutex abgerufen werden kann. Die verwandte pthread_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 ausgegeben und ist jetzt aktiv.

Ein kritischer Abschnitt bezieht sich auf eine Code-Region, in der Nicht-Determinismus 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 Synchronisierung vorhanden ist. Eine Mutex-Sperre ist eine Form der Synchronisation.

4
chrisaycock

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 darauf hinweist, dass die Sperre nicht erhalten werden konnte, wenn ein anderer Benutzer sie bereits gesperrt hat.

Mutex ist eigentlich nur eine vereinfachte Semaphore. Wenn Sie über sie lesen und sie verstehen, verstehen Sie Mutexe. Es gibt mehrere Fragen zu Mutexen und Semaphoren in SO. Unterschied zwischen binärem Semaphor und Mutex , Wann sollten wir Mutex verwenden und wann sollten Semaphore und so weiter verwendet werden? Das Toilettenbeispiel in der ersten Verbindung ist ungefähr so ​​gut, wie man es sich vorstellen kann. Der einzige Code besteht darin, zu prüfen, ob der Schlüssel verfügbar ist, und wenn dies der Fall ist, wird er reserviert. Beachten Sie, dass Sie nicht wirklich die Toilette selbst reservieren, sondern den Schlüssel.

3
Makis

SEMAPHORE-BEISPIEL ::

sem_t m;
sem_init(&m, 0, 0); // initialize semaphore to 0

sem_wait(&m);
// critical section here
sem_post(&m);

Referenz: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt

2
parasrish

Für diejenigen, die nach dem Shortex-Mutex-Beispiel suchen:

#include <mutex>
using namespace std;

int main() {
    mutex m;

    m.lock();
    // do thread-safe stuff
    m.unlock();

    return 0;
}
0
nathangeorge1