Eine sehr detaillierte Erklärung der Linux C++ Multi-Thread-Synchronisierung

Eine sehr detaillierte Erklärung der Linux C++ Multi-Thread-Synchronisierung

Hintergrundfrage: Welche Probleme entstehen in einem bestimmten Anwendungsszenario durch die Nichtsynchronisierung mehrerer Threads?

Nehmen wir als Beispiel die Multithread-Simulation des Ticketverkaufs an mehreren Schaltern:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>

Namespace std verwenden;

int ticket_sum=20;
void *Ticket_verkaufen(void *Argument)
{
    für (int i = 0; i < 20; i++)
    {
        wenn(Ticketsumme>0)
        {
            Schlaf (1);
            cout<<"verkaufe das "<<20-ticket_sum+1<<"te"<<endl;
            Ticketsumme--;
        }
    }
    gebe 0 zurück;
}

int main()
{
    int-Flagge;
    pthread_t tids[4];

    für (int i = 0; i < 4; i++)
    {
        Flag = pthread_create (&tids[i], NULL, &sell_ticket, NULL);
        wenn(Flagge)
        {
            cout<<"pthread-Erstellungsfehler, flag="<<flag<<endl;
            Flagge zurückgeben;
        }
    }

    Schlaf (20);
    ungültig *ans;
    für (int i = 0; i < 4; i++)
    {
        Flag=pthread_join(tids[i],&ans);
        wenn(Flagge)
        {
            cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
            Flagge zurückgeben;
        }
        cout<<"ans="<<ans<<endl;
    }
    gebe 0 zurück;
}

Analyse: Es gibt insgesamt nur 20 Tickets, aber 23 Tickets wurden verkauft. Dies ist offensichtlich ein Überkauf- und Überverkaufsproblem. Die Hauptursache dieses Problems ist, dass alle Threads gleichzeitig Ticket_Sum lesen und schreiben können!

ps:

1. Bei Parallelität wird die Reihenfolge, in der Anweisungen ausgeführt werden, vom Kernel bestimmt. Innerhalb desselben Threads werden Anweisungen der Reihe nach ausgeführt, es ist jedoch schwierig zu erkennen, welche Anweisung zwischen verschiedenen Threads zuerst ausgeführt wird. Wenn das Ergebnis der Operation von der Reihenfolge abhängt, in der verschiedene Threads ausgeführt werden, entsteht ein Race Condition. In diesem Fall ist das Ergebnis der Berechnung schwer vorherzusagen, daher sollte die Entstehung von Race Conditions so weit wie möglich vermieden werden.

2. Die gebräuchlichste Methode zum Lösen von Race Conditions besteht darin, die beiden zuvor getrennten Anweisungen zu einer unteilbaren atomaren Operation zu kombinieren. In die atomare Operation können keine anderen Aufgaben eingefügt werden!

3. Beim Multithreading bedeutet Synchronisierung, dass innerhalb eines bestimmten Zeitraums nur einem Thread der Zugriff auf eine Ressource gestattet ist und anderen Threads während dieses Zeitraums der Zugriff auf die Ressource nicht gestattet ist!

4. Allgemeine Methoden zur Thread-Synchronisierung: Mutex-Sperren, Bedingungsvariablen, Lese-/Schreibsperren, Semaphoren

1. Mutex

Es handelt sich im Wesentlichen um eine spezielle globale Variable mit zwei Zuständen: gesperrt und entsperrt. Der entsperrte Mutex kann von einem Thread abgerufen werden. Nach dem Abrufen wird der Mutex gesperrt und in den gesperrten Zustand versetzt. Danach hat nur der Thread das Recht, die Sperre zu öffnen. Wenn andere Threads den Mutex abrufen möchten, müssen sie warten, bis der Mutex erneut geöffnet wird.

Verwenden Sie Mutex-Sperren, um Ressourcen zu synchronisieren:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>

Namespace std verwenden;

int ticket_sum=20;
pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER; //statisches Initialisieren von Mutex

void *Ticket_verkaufen(void *Argument)
{
    für (int i = 0; i < 20; i++)
    {
        pthread_mutex_lock(&mutex_x);//atomare Operation durch Mutex-Sperre
        wenn(Ticketsumme>0)
        {
            Schlaf (1);
            cout<<"verkaufe das "<<20-ticket_sum+1<<"te"<<endl;
            Ticketsumme--;
        }
        pthread_mutex_unlock(&mutex_x);
    }
    gebe 0 zurück;
}

int main()
{
    int-Flagge;
    pthread_t tids[4];

    für (int i = 0; i < 4; i++)
    {
        Flag = pthread_create (&tids[i], NULL, &sell_ticket, NULL);
        wenn(Flagge)
        {
            cout<<"pthread-Erstellungsfehler, flag="<<flag<<endl;
            Flagge zurückgeben;
        }
    }

    Schlaf (20);
    ungültig *ans;
    für (int i = 0; i < 4; i++)
    {
        Flag=pthread_join(tids[i],&ans);
        wenn(Flagge)
        {
            cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
            Flagge zurückgeben;
        }
        cout<<"ans="<<ans<<endl;
    }
    gebe 0 zurück;
}

Analyse: Durch das Hinzufügen einer Mutex-Sperre zum Kerncodesegment des Ticketverkaufs wird es zu einer atomaren Operation! Wird nicht von anderen Threads beeinflusst

1. Initialisierung des Mutex

Die Initialisierung von Mutex ist in statische Initialisierung und dynamische Initialisierung unterteilt

Statisch: pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER; //statisches Init-Mutex

Dynamisch: pthread_mutex_init-Funktion

PS: Was ist der Unterschied zwischen der statischen und der dynamischen Initialisierung eines Mutex?

Muss ergänzt werden. . . .

2. Verwandte Eigenschaften und Klassifizierung von Mutex-Sperren

//Mutex-Attribute initialisieren pthread_mutexattr_init(pthread_mutexattr_t attr);

//Zerstöre das Mutex-Attribut pthread_mutexattr_destroy(pthread_mutexattr_t attr);

// Wird verwendet, um das Mutex-Sperrattribut abzurufen int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);

//Wird zum Festlegen von Mutex-Sperrattributen verwendet int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

attr repräsentiert die Attribute des Mutex

pshared stellt das gemeinsame Attribut der Mutex-Sperre dar und hat zwei mögliche Werte:

1) PTHREAD_PROCESS_PRIVATE: Die Sperre kann nur zum gegenseitigen Ausschluss zwischen zwei Threads innerhalb eines Prozesses verwendet werden (Standard)

2) PTHREAD_PROCESS_SHARED: Die Sperre kann zum gegenseitigen Ausschluss von Threads in zwei verschiedenen Prozessen verwendet werden. Bei der Verwendung müssen Sie auch einen Mutex im gemeinsam genutzten Prozessspeicher zuordnen und dann die Attribute für den Mutex angeben.

Klassifizierung von Mutex-Sperren:

//Den Mutex-Typ abrufen int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);

//Den Mutex-Typ festlegen int pthread_mutexattr_settype(const pthread_mutexattr_t *restrict attr, int type);

Der Parametertyp gibt den Typ der Mutex-Sperre an. Es gibt die folgenden vier Typen:

1.PTHREAD_MUTEX_NOMAL: Standard-Mutex-Sperre, die erste Sperre ist erfolgreich, die zweite Sperre schlägt fehl und blockiert

2. PTHREAD_MUTEX_RECURSIVE: rekursives Mutex-Lock. Das erste Lock ist erfolgreich und das zweite Lock ist ebenfalls erfolgreich. Dies kann als interner Zähler verstanden werden. Jeder Lock-Zähler ist um 1 erhöht und der Unlock-Zähler um 1 verringert.

3.PTHREAD_MUTEX_ERRORCHECK: Überprüfen Sie die Mutex-Sperre. Die erste Sperre ist erfolgreich. Die zweite Sperre gibt eine Fehlermeldung zurück, ohne zu blockieren.

4.PTHREAD_MUTEX_DEFAULT: Standard-Mutex-Sperre, die erste Sperre ist erfolgreich, die zweite Sperre schlägt fehl

3. Testen Sie die Sperrfunktion

int pthread_mutex_lock(&mutex): Testen Sie, ob die Sperrfunktion EBUSY zurückgibt, anstatt zu hängen und zu warten, wenn die Sperre bereits belegt ist. Wenn die Sperre nicht belegt ist, kann die Sperre natürlich abgerufen werden.

Um die Situation deutlich zu machen, in der zwei Threads um Ressourcen konkurrieren, lassen wir eine Funktion die Testsperrfunktion zum Sperren verwenden und die andere die normale Sperrfunktion zum Sperren.

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <errno.h>
Namespace std verwenden;

int ticket_sum=20;
pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER; //statisches Initialisieren von Mutex

void *ticket_1_verkaufen(void *arg)
{
    für (int i = 0; i < 20; i++)
    {
        pthread_mutex_lock(&mutex_x);
        wenn(Ticketsumme>0)
        {
            Schlaf (1);
            cout<<"thread_1 verkaufe das "<<20-ticket_sum+1<<"te Ticket"<<endl;
            Ticketsumme--;
        }
        Schlaf (1);
        pthread_mutex_unlock(&mutex_x);
        Schlaf (1);
    }
    gebe 0 zurück;
}

void *ticket_2_verkaufen(void *arg)
{
    int-Flagge;
    für (int i = 0; i < 10; i++)
    {
        Flagge=pthread_mutex_trylock(&mutex_x);
        wenn(flag==EBUSY)
        {
            cout<<"sell_ticket_2:die Variable ist durch sell_ticket_1 gesperrt"<<endl;
        }
        sonst wenn(Flag==0)
        {
            wenn(Ticketsumme>0)
            {
                Schlaf (1);
                cout<<"thread_2 verkaufe das "<<20-ticket_sum+1<<"te Ticket"<<endl;
                Ticketsumme--;
            }
            pthread_mutex_unlock(&mutex_x);
        }
        Schlaf (1);
    }
    gebe 0 zurück;
}
int main()
{
    int-Flagge;
    pthread_t tids[2];

    Flag = pthread_create (&tids[0], NULL, &sell_ticket_1, NULL);
    wenn(Flagge)
    {
        cout<<"pthread-Erstellungsfehler, flag="<<flag<<endl;
        Flagge zurückgeben;
    }

    Flag = pthread_create (&tids[1], NULL, &sell_ticket_2, NULL);
    wenn(Flagge)
    {
        cout<<"pthread-Erstellungsfehler, flag="<<flag<<endl;

        Flagge zurückgeben;
    }

    ungültig *ans;
    Schlaf (30);
    Flag=pthread_join(tids[0],&ans);
    wenn(Flagge)
    {
        cout<<"tid="<<tids[0]<<"join erro flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    anders
    {
        cout<<"ans="<<ans<<endl;
    }

    Flag=pthread_join(tids[1],&ans);
    wenn(Flagge)
    {
        cout<<"tid="<<tids[1]<<"join erro flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    anders
    {
        cout<<"ans="<<ans<<endl;
    }

    gebe 0 zurück;
}

Analyse: Durch Testen der Sperrfunktion können wir deutlich die Situation erkennen, in der zwei Threads um Ressourcen konkurrieren.

2. Bedingungsvariablen

Mutexe sind nicht allmächtig. Wenn ein Thread beispielsweise auf das Eintreten einer Bedingung in gemeinsam genutzten Daten wartet, muss er das Datenobjekt möglicherweise wiederholt sperren und entsperren (Polling). Ein solches Polling ist jedoch sehr zeit- und ressourcenintensiv und sehr ineffizient, sodass Mutex-Sperren für diese Situation nicht geeignet sind.

Wir benötigen eine Methode, die den Thread in den Ruhezustand versetzt, während er auf die Erfüllung einer bestimmten Bedingung wartet, und die, sobald die Bedingung erfüllt ist, den Thread wechselt, der in den Ruhezustand versetzt wurde, während er auf die Erfüllung einer bestimmten Bedingung wartet.

Wenn wir eine solche Methode implementieren können, wird die Effizienz des Programms zweifellos erheblich verbessert, und diese Methode ist die Bedingungsvariable!

Beispiel:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <errno.h>
Namespace std verwenden;

pthread_cond_t qready=PTHREAD_COND_INITIALIZER; //cond
pthread_mutex_t qlock=PTHREAD_MUTEX_INITIALIZER; //mutex

int x=10,y=20;

void *f1(void *arg)
{
  cout<<"f1 start"<<endl;
  pthread_mutex_lock(&qlock);
  während(x<y)
  {
    pthread_cond_wait(&qready,&qlock);
  }
  pthread_mutex_unlock(&qlock);
  Schlaf (3);
  cout<<"f1 end"<<endl;
  gebe 0 zurück;
}

void *f2(void *arg)
{
  cout<<"f2 start"<<endl;
  pthread_mutex_lock(&qlock);
  x=20;
  y=10;
  cout<<"hat eine Änderung,x="<<x<<" y="<<y<<endl;
  pthread_mutex_unlock(&qlock);
  wenn(x>y)
  {
    pthread_cond_signal(&qready);
  }
  cout<<"f2 end"<<endl;
  gebe 0 zurück;
}

int main()
{
  pthread_t tids[2];
  int-Flagge;

  Flag = pthread_create (&tids[0], NULL, f1, NULL);
  wenn(Flagge)
  {
    cout<<"pthread 1 Erstellungsfehler "<<endl;
    Flagge zurückgeben;
  }

  schlaf(2);

  Flag = pthread_create (&tids[1], NULL, f2, NULL);
  wenn(Flagge)
  {
    cout<<"pthread 2 erstellt Fehler "<<endl;
    Flagge zurückgeben;
  }

  Schlaf (5);
  gebe 0 zurück;
}

Analyse: Thread 1 wird blockiert, da die Bedingung nicht erfüllt ist. Dann wird Thread 2 ausgeführt und ändert die Bedingung. Thread 2 sendet eine Benachrichtigung über die Änderung der Bedingung an Thread 1. Dann endet Thread 2. Dann wird Thread 1 weiter ausgeführt und dann endet Thread 1. Um sicherzustellen, dass Thread 1 zuerst ausgeführt wird, schlafen wir 2 Sekunden, bevor wir Thread 2 erstellen.

ps:

1. Bedingungsvariablen gleichen die Mängel von Mutex-Sperren aus, indem sie den laufenden Thread blockieren und darauf warten, dass ein anderer Thread ein Signal sendet. Sie werden häufig zusammen mit Mutex-Sperren verwendet. Bei Verwendung werden Bedingungsvariablen verwendet, um einen Thread zu blockieren. Wenn die Bedingung nicht erfüllt ist, entsperrt der Thread häufig die entsprechende Mutex-Sperre und wartet auf eine Änderung der Bedingung. Sobald ein anderer Thread die Bedingungsvariable ändert, benachrichtigt er die entsprechende Bedingungsvariable, um die Zeilen zu wechseln. Ein oder mehrere Threads, die durch diese Bedingungsvariable blockiert sind, sperren die Mutex-Sperre erneut und testen erneut, ob die Bedingung erfüllt ist.

1. Verwandte Funktionen von Bedingungsvariablen

1) Erstellen

Statische Methode: pthread_cond_t cond PTHREAD_COND_INITIALIZER

Dynamische Methode: int pthread_cond_init(&cond,NULL)

Die vom Linux-Thread implementierten Bedingungsvariablen unterstützen keine Attribute, daher NULL (cond_attr-Parameter)

2) Abmelden

int pthread_cond_destory(&cond)

Die Bedingungsvariable kann nur abgemeldet werden, wenn sich kein Thread auf der Bedingungsvariablen befindet, andernfalls wird EBUSY zurückgegeben.

Da die von Linux implementierten Bedingungsvariablen keine Ressourcen zuweisen, umfasst die Abmeldeaktion nur die Überprüfung, ob wartende Threads vorhanden sind! (Bitte beachten Sie die zugrunde liegende Implementierung der Bedingungsvariablen.)

3) Warten

Bedingtes Warten: int pthread_cond_wait(&cond,&mutex)

Zeitgesteuertes Warten: int pthread_cond_timewait(&cond,&mutex,time)

1. Wenn die Bedingung vor der angegebenen Zeit nicht erfüllt wird, wird ETIMEOUT zurückgegeben und die Wartezeit endet.

2. Unabhängig von der Wartemethode muss eine Mutex-Sperre vorhanden sein, um zu verhindern, dass mehrere Threads gleichzeitig pthread_cond_wait anfordern und es zu einem Race Condition kommt!

3. Der Thread muss gesperrt werden, bevor pthread_cond_wait aufgerufen wird

4) Stimulation

Einen wartenden Thread stimulieren: pthread_cond_signal(&cond)

Stimuliere alle wartenden Threads: pthread_cond_broadcast(&cond)

Wichtig ist, dass pthread_cond_signal nicht den „Donnernden Herdeneffekt“ hat, d. h., es sendet ein Signal an höchstens einen wartenden Thread und sendet kein Signal an alle Threads, um sie aufzuwecken und sie dann aufzufordern, selbst um Ressourcen zu konkurrieren!

pthread_cond_signal bestimmt basierend auf der Priorität und Wartezeit des wartenden Threads, welcher wartende Thread ausgelöst werden soll.

Schauen wir uns ein Programm an und finden wir die Probleme damit.

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <errno.h>
Namespace std verwenden;

pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //Taxi-Ankunftsbedingung
pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER; // Mutex synchronisieren

void *Reisender_ankommen(void *Name)
{
    cout<<"Traveler:"<<(char*)name<<" braucht jetzt ein Taxi!"<<endl;
    pthread_mutex_lock(&taxi_mutex);
    pthread_cond_wait(&taxi_cond,&taxi_mutex);
    pthread_mutex_unlock(&taxi_mutex);
    cout<<"Traveller:"<<(char*)name<<" habe jetzt ein Taxi!"<<endl;
    pthread_exit((void*)0);
}

void *taxi_ankommen(void *name)
{
    cout<<"Taxi:"<<(char*)name<<" Ankömmling."<<endl;
    pthread_cond_signal(&taxi_cond);
    pthread_exit((void*)0);
}

int main()
{
    pthread_t tids[3];
    int-Flagge;

    Flag = pthread_create (&tids[0], NULL, taxi_arrive, (void*)("Jack"));
    wenn(Flagge)
    {
        cout<<"pthread_create Fehler:flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    cout<<"die Zeit vergeht"<<endl;
    Schlaf (1);

    Flagge = pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));
    wenn(Flagge)
    {
        cout<<"pthread_create Fehler:flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    cout<<"die Zeit vergeht"<<endl;
    Schlaf (1);

    Flag = pthread_create (&tids[2], NULL, taxi_arrive, (void*)("Mike"));
    wenn(Flagge)
    {
        cout<<"pthread_create Fehler:flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    cout<<"die Zeit vergeht"<<endl;
    Schlaf (1);

    ungültig *ans;
    für (int i = 0; i < 3; i++)
    {
        Flag=pthread_join(tids[i],&ans);
        wenn(Flagge)
        {
            cout<<"pthread_join Fehler:flag="<<flag<<endl;
            Flagge zurückgeben;
        }
        cout<<"ans="<<ans<<endl;
    }
    gebe 0 zurück;
}

Analyse: Das Programm besteht aus einer Bedingungsvariablen, die die Passagiere daran erinnert, dass ein Taxi angekommen ist, und einer Synchronisationssperre. Nachdem die Passagierin angekommen ist, wartet sie auf das Auto (Bedingungsvariable). Nachdem das Taxi angekommen ist, wird die Passagierin benachrichtigt. Wir können sehen, dass die Passagierin Susan nach ihrer Ankunft nicht Jacks Auto nahm, das zuerst ankam, sondern wartete, bis Mikes Auto ankam, bevor sie Mikes Auto nahm. Jacks Auto stand im Leerlauf. Warum ist das passiert? Lassen Sie uns den Code analysieren: Wir stellen fest, dass er nach der Ankunft von Jacks Taxi pthread_cond_signal(&taxi_cond) aufruft und feststellt, dass keine Passagiere da sind, sodass er den Thread direkt beendet. . . .

Die korrekte Vorgehensweise sollte folgendermaßen aussehen: Jack, der zuerst ankam, stellte fest, dass keine Passagiere da waren, und wartete dann auf Passagiere. Wenn Passagiere ankamen, würde er direkt losfahren, und wir sollten die Anzahl der Passagiere zählen.

Nehmen Sie die folgenden Verbesserungen vor:

1. Fügen Sie einen Passagierzähler hinzu, damit das Taxi direkt nach der Ankunft eines Passagiers losfahren kann, anstatt auf andere Passagiere zu warten (toter Thread)

2. Fügen Sie der Taxi-Ankunftsfunktion eine While-Schleife hinzu. Wenn keine Passagiere vorhanden sind, wird gewartet, bis der Passagier eintrifft.

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <errno.h>
Namespace std verwenden;

pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //Taxi-Ankunftsbedingung
pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER; // Mutex synchronisieren


void *Reisender_ankommen(void *Name)
{
    cout<<"Traveler:"<<(char*)name<<" braucht jetzt ein Taxi!"<<endl;
    pthread_mutex_lock(&taxi_mutex);
 
    pthread_cond_wait(&taxi_cond,&taxi_mutex);
    pthread_mutex_unlock(&taxi_mutex);
    cout<<"Traveller:"<<(char*)name<<" habe jetzt ein Taxi!"<<endl;
    pthread_exit((void*)0);
}

void *taxi_ankommen(void *name)
{
    cout<<"Taxi:"<<(char*)name<<" Ankömmling."<<endl;
    
    pthread_exit((void*)0);
}

int main()
{
    pthread_t tids[3];
    int-Flagge;

    Flag = pthread_create (&tids[0], NULL, taxi_arrive, (void*)("Jack"));
    wenn(Flagge)
    {
        cout<<"pthread_create Fehler:flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    cout<<"die Zeit vergeht"<<endl;
    Schlaf (1);

    Flagge = pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));
    wenn(Flagge)
    {
        cout<<"pthread_create Fehler:flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    cout<<"die Zeit vergeht"<<endl;
    Schlaf (1);

    Flag = pthread_create (&tids[2], NULL, taxi_arrive, (void*)("Mike"));
    wenn(Flagge)
    {
        cout<<"pthread_create Fehler:flag="<<flag<<endl;
        Flagge zurückgeben;
    }
    cout<<"die Zeit vergeht"<<endl;
    Schlaf (1);

    ungültig *ans;
    für (int i = 0; i < 3; i++)
    {
        Flag=pthread_join(tids[i],&ans);
        wenn(Flagge)
        {
            cout<<"pthread_join Fehler:flag="<<flag<<endl;
            Flagge zurückgeben;
        }
        cout<<"ans="<<ans<<endl;
    }
    gebe 0 zurück;
}

3. Lese-/Schreibsperre

Mehrere Threads können gleichzeitig lesen, aber nicht gleichzeitig schreiben

1. Lese-/Schreibsperren sind anwendbarer und paralleler als Mutex-Sperren

2. Lese-/Schreibsperren eignen sich am besten für Situationen, in denen die Anzahl der Lesevorgänge auf Datenstrukturen die Anzahl der Schreibvorgänge übersteigt!

3. Wenn sich die Sperre im Lesemodus befindet, kann sie von Threads gemeinsam genutzt werden. Wenn sich die Sperre jedoch im Schreibmodus befindet, kann sie nur exklusiv sein. Daher wird die Lese-/Schreibsperre auch als gemeinsam genutzte exklusive Sperre bezeichnet.

4. Es gibt zwei Strategien für Lese-/Schreibsperren: starke Lesesynchronisation und starke Schreibsynchronisation

Bei der starken Lesesynchronisation wird Lesern immer die höhere Priorität eingeräumt. Solange Schreiber keine Schreiboperationen durchführen, können Leser Zugriffsrechte erhalten.

Bei der starken Schreibsynchronisierung wird den Schreibvorgängen immer eine höhere Priorität eingeräumt und die Lesevorgänge können erst dann lesen, wenn alle wartenden oder ausgeführten Schreibvorgänge abgeschlossen sind.

Verschiedene Systeme verwenden unterschiedliche Strategien. Beispielsweise verwendet das Flugbuchungssystem eine starke Schreibsynchronisierung, während das Bibliotheksreferenzsystem eine starke Lesesynchronisierung verwendet.

Wenden Sie je nach Geschäftsszenario unterschiedliche Strategien an

1) Initialisierte Zerstörung der Lese-/Schreibsperre

Statische Initialisierung: pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER

Dynamische Initialisierung: int pthread_rwlock_init(rwlock, NULL), NULL bedeutet, dass die Lese-/Schreibsperre die Standardattribute verwendet

Zerstören Sie die Lese-/Schreibsperre: int pthread_rwlock_destory(rwlock)

Bevor Sie die Ressourcen einer Lese-/Schreibsperre freigeben, müssen Sie die Lese-/Schreibsperre mit der Funktion pthread_rwlock_destory bereinigen. Gibt von der Funktion pthread_rwlock_init zugewiesene Ressourcen frei.

Wenn Sie möchten, dass die Lese-/Schreibsperre nicht standardmäßige Attribute verwendet, darf attr nicht NULL sein und Sie müssen attr einen Wert zuweisen.

int pthread_rwlockattr_init(attr), attr initialisieren

int pthread_rwlockattr_destory(attr), Attr zerstören

2) Erwerben Sie die Sperre im Schreibmodus, erwerben Sie die Sperre im Lesemodus und geben Sie die Lese-/Schreibsperre frei

int pthread_rwlock_rdlock(rwlock), Sperre im Lesemodus erwerben

int pthread_rwlock_wrlock(rwlock), Sperre im Schreibmodus erwerben

int pthread_rwlock_unlock(rwlock), löse die Sperre

Die beiden oben genannten Möglichkeiten zum Erwerben von Sperren sind beides blockierende Funktionen, d. h. wenn die Sperre nicht erlangt werden kann, kehrt der aufrufende Thread nicht sofort zurück, sondern blockiert die Ausführung. Wenn ein Schreibvorgang erforderlich ist, ist diese blockierende Art des Erwerbs von Sperren sehr schlecht. Denken Sie darüber nach, ich muss schreiben, habe nicht nur die Sperre nicht erhalten, ich muss hier auch noch warten, was die Effizienz erheblich verringert.

Daher sollten wir die Sperre auf eine nicht blockierende Weise erwerben:

int pthread_rwlock_tryrdlock(rwlock)

int pthread_rwlock_trywrlock(rwlock)

Beispiel für eine Lese-/Schreibsperre:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <errno.h>
Namespace std verwenden;

Int-Nummer = 5;
pthread_rwlock_t rwlock;

void * Leser(void * arg)
{
  pthread_rwlock_rdlock(&rwlock);
  cout<<"reader "<<(long)arg<<" hat die Sperre erhalten"<<endl;
  pthread_rwlock_unlock(&rwlock);
  gebe 0 zurück;
}

void *Schreiber(void *Arg)
{
  pthread_rwlock_wrlock(&rlock);
  cout<<"writer "<<(long)arg<<" hat die Sperre erhalten"<<endl;
  pthread_rwlock_unlock(&rwlock);
  gebe 0 zurück;
}

int main()
{
  int-Flagge;
  lang n=1,m=1;
  pthread_t wid,rid;
  pthread_attr_t-Attr;

  flag=pthread_rwlock_init(&rwlock,NULL);
  wenn(Flagge)
  {
    cout<<"rwlock-Init-Fehler"<<endl;
    Flagge zurückgeben;
  }

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//Thread trennen

  für (int i = 0; i <num; i++)
  {
    wenn(i%3)
    {
      pthread_create(&rid,&attr,reader,(void *)n);
      cout<<"Leser erstellen "<<n<<endl;
      n++;
    }anders
    {
      pthread_create(&wid,&attr,writer,(void *)m);
      cout<<"Writer erstellen "<<m<<endl;
      m++;
    }
  }

  sleep(5);//warten, sonst fertig
  gebe 0 zurück;
}

Analyse: 3 Lese-Threads, 2 Schreib-Threads, mehr Lese-Threads als Schreib-Threads

Wenn sich die Lese-/Schreibsperre im Schreibzustand befindet, werden alle Threads, die versuchen, die Sperre zu sperren, blockiert, bevor die Sperre entsperrt wird.

Wenn sich die Lese-/Schreibsperre im Lesezustand befindet, können vor dem Entsperren der Sperre alle Threads, die versuchen, sie im Lesemodus zu sperren, Zugriff erhalten. Threads, die versuchen, sie im Schreibmodus zu sperren, werden jedoch blockiert.

Daher ist die Lese-/Schreibsperre standardmäßig im starken Lesemodus!

4. Semaphor

Der Unterschied zwischen einem Semaphor (SEM) und einer Mutex-Sperre: Eine Mutex-Sperre erlaubt nur einem Thread, den kritischen Abschnitt zu betreten, während ein Semaphor mehreren Threads erlaubt, den kritischen Abschnitt zu betreten

1) Semaphor-Initialisierung

int sem_init(&sem,pshared,v)

pshared ist 0, was darauf hinweist, dass dieser Semaphor ein lokaler Semaphor des aktuellen Prozesses ist.

pshared ist 1, was bedeutet, dass dieser Semaphor von mehreren Prozessen gemeinsam genutzt werden kann.

v ist der Anfangswert des Semaphors

Gibt 0 zurück, wenn erfolgreich, -1, wenn fehlgeschlagen

2) Addition und Subtraktion von Signalwerten

int sem_wait(&sem): dekrementiert den Wert des Semaphors in einer atomaren Operation um 1

int sem_post(&sem): addiert 1 zum Semaphorwert in einer atomaren Operation

3) Bereinigen Sie das Semaphor

int sem_destory(&sem)

Verwenden Sie Semaphoren, um den Prozess der Bedienung von 2 Fenstern und 10 Gästen zu simulieren

Beispiel:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
Namespace std verwenden;


int num=10;
sem_t sem;

void *get_service(void *cid)
{
  id=*((int*)cid);
  wenn(sem_wait(&sem)==0)
  {
     Schlaf (5);
     cout<<"Kunde "<<id<<" den Dienst erhalten"<<endl;
     cout<<"Kunde "<<id<<" fertig "<<endl;
     sem_post(&sem);
  }
  gebe 0 zurück;
}

int main()
{
  sem_init(&sem,0,2);
  pthread_t Kunde[num];
  int-Flagge;

  für (int i = 0; i <num; i++)
  {
    }
    Flag = pthread_create (& Kunde [i], NULL, get_service, & id);
    wenn(Flagge)
    {
      cout<<"Fehler beim Erstellen des PThreads"<<endl;
      Flagge zurückgeben;
    }anders
    {
      cout<<"Kunde "<<i<<" angekommen "<<endl;
    }
    Schlaf (1);
  }

  //warte, bis alle Threads fertig sind
  für (int j=0;j<num;j++)
  {
    pthread_join(Kunde[j],NULL);
  }
  sem_destroy(&sem);
  gebe 0 zurück;
}

Analyse: Der Wert des Semaphors stellt ein inaktives Servicefenster dar. Jedes Fenster kann jeweils nur eine Person bedienen. Wenn ein inaktives Fenster vorhanden ist, ist der Semaphor vor Beginn des Services -1 und nach Abschluss des Services +1.

Zusammenfassung: Vier Möglichkeiten der Linux C++-Thread-Synchronisierung: Mutex-Sperren, bedingte Variablen, Lese-/Schreibsperren, Semaphoren

Damit ist dieser Artikel mit der äußerst detaillierten Erklärung der Multithread-Synchronisierungsmethoden für Linux C++ abgeschlossen. Weitere relevante Inhalte zur Multithread-Synchronisierung für Linux C++ finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • So implementieren Sie Multithreading und Thread-Synchronisierung in C++
  • Warum verwendet C++-Multithreading bedingte Variablen?
  • C++-Multithreading zur Implementierung eines TCP-Servers, der gleichzeitig mit mehreren Clients kommuniziert
  • Praxis der Multithread-Operationen mit neuen Funktionen in C++11
  • Detaillierte Erläuterung der C++-Multithreading-Rückgabewertmethode
  • C++-Multithreading zur Implementierung eines elektronischen Wörterbuchs
  • C++11 Parallele Programmierung: Multithread-std::thread
  • Tutorial zur Verwendung von Sperren und Bedingungsvariablen in C++-Multithreading
  • Analyse des Auftretens von Deadlocks beim C++-Multithreading (einschließlich zwei Zusammenfassungen und sechs Beispielen)
  • Details zur erzwungenen Beendigung von C++-Multithreads

<<:  Eine kurze Analyse der Unterschiede zwischen px, rem, em, vh und vw in CSS

>>:  Ein Beispiel für den Unterschied zwischen den ID- und Name-Attributen in der Eingabe

Artikel empfehlen

Nginx leitet dynamisch an Upstream weiter, entsprechend dem Pfad in der URL

In Nginx gibt es einige erweiterte Szenarien, in ...

Zusammenfassung der Linux Logical Volume Management (LVM)-Nutzung

Die Verwaltung des Speicherplatzes ist für System...

Konfigurationshandbuch für den Lastenausgleich auf Ebene 4 von Nginx

1. Einführung in Layer 4 Load Balancing Was ist L...

5 Möglichkeiten, den diagonalen Kopfzeileneffekt in der Tabelle zu erzielen

Jeder muss mit Tabellen vertraut sein. Wir stoßen...

Detaillierte Erklärung wichtiger Kaskadierungskonzepte in CSS

Kürzlich stieß ich im Verlauf des Projekts auf ei...

Eine kurze Diskussion über allgemeine Operationen von MySQL in cmd und Python

Umgebungskonfiguration 1: Installieren Sie MySQL ...

Beispiel für utf8mb4-Sortierung in MySQL

Allgemeine utf8mb4-Sortierregeln in MySQL sind: u...

Detaillierte Erläuterung des Linux-Befehls zur Änderung des Hostnamens

Linux-Befehl zum Ändern des Hostnamens 1. Wenn Si...

So verbinden Sie XShell und Netzwerkkonfiguration in CentOS7

1. Linux-Netzwerkkonfiguration Bevor Sie das Netz...

Analysieren Sie, wie eine SQL-Abfrageanweisung in MySQL ausgeführt wird

Inhaltsverzeichnis 1. Übersicht über die logische...

MySQL 5.7.20 Zip-Installations-Tutorial

MySQL 5.7.20 Zip-Installation, der spezifische In...