Detaillierte Erklärung der Verwendung und Unterschiede verschiedener Sperrmechanismen in Linux

Detaillierte Erklärung der Verwendung und Unterschiede verschiedener Sperrmechanismen in Linux

Vorwort:

Ich glaube, dass diejenigen, die dieses Wissen verstehen müssen, bereits über ein grundlegendes Verständnis der Interprozesskommunikation und der Interthread-Kommunikation verfügen. Einer der Mechanismen der Interprozesskommunikation ist beispielsweise der gemeinsam genutzte Speicher (hier nicht im Detail erläutert): Mehrere Prozesse können gleichzeitig auf denselben Speicherblock zugreifen. Wenn der kritische Abschnitt, der auf diesen Speicher zugreift, nicht gegenseitig exklusiv oder synchronisiert ist, kann es bei der Ausführung des Prozesses zu unvorhersehbaren Fehlern und Ergebnissen kommen.

Als Nächstes lernen wir drei gängige gegenseitige Ausschlussoperationen unter Linux kennen –> Sperren.

1. Mutex

Features: Für Leser und Autoren. Solange eine Partei die Sperre erwirbt, kann die andere Partei nicht weiterhin damit fortfahren, sie zu erwerben und den kritischen Abschnittscode auszuführen.

Erstellen Sie ein Schloss:

Es gibt zwei Möglichkeiten, ein Mutex zu erstellen: statisch und dynamisch. POSIX definiert ein Makro PTHREAD_MUTEX_INITIALIZER, um das Mutex statisch zu initialisieren.

So geht's:

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

In der LinuxThreads-Implementierung ist pthread_mutex_t eine Struktur und PTHREAD_MUTEX_INITIALIZER eine Strukturkonstante.

Die dynamische Methode besteht darin, die Funktion pthread_mutex_init() zu verwenden, um die Mutex-Sperre zu initialisieren. Die API-Definition lautet wie folgt:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t*mutexattr)

mutexattr wird verwendet, um die Mutex-Attribute anzugeben (siehe unten). Wenn es NULL ist, werden die Standardattribute verwendet. pthread_mutex_destroy() wird verwendet, um einen Mutex aufzuheben. Die API-Definition lautet wie folgt:

int pthread_mutex_destroy(pthread_mutex_t *mutex)

Die Sperrvorgänge umfassen hauptsächlich das Sperren von pthread_mutex_lock(), das Entsperren von pthread_mutex_unlock() und das Testen der Sperre von pthread_mutex_trylock(). Unabhängig von der Art der Sperre ist es unmöglich, dass zwei verschiedene Threads sie gleichzeitig erhalten, und sie müssen warten, bis sie entsperrt werden. Bei gewöhnlichen Sperren und adaptiven Sperrtypen kann der Entsperrer ein beliebiger Thread im selben Prozess sein; während die Fehlererkennungssperre vom Sperreninhaber entsperrt werden muss, um wirksam zu sein, andernfalls wird EPERM zurückgegeben; bei verschachtelten Sperren erfordern die Dokumentation und Implementierung, dass der Sperreninhaber die Sperre entsperren muss, aber experimentelle Ergebnisse zeigen, dass es keine solche Einschränkung gibt. Dieser Unterschied wurde noch nicht erklärt. Wenn ein Thread im selben Prozess einen Block sperrt, ihn aber nicht entsperrt, kann kein anderer Thread die Sperre erhalten.

int pthread_mutex_lock(pthread_mutex_t *mutex) 
int pthread_mutex_unlock(pthread_mutex_t *mutex) 
int pthread_mutex_trylock(pthread_mutex_t *mutex)

Die Semantik von pthread_mutex_trylock() ähnelt der von pthread_mutex_lock(), mit der Ausnahme, dass EBUSY zurückgegeben wird, anstatt zu warten, wenn die Sperre bereits belegt ist.
Beispiel: Threadsichere Sperre im Singleton-Modus:

Klasse SingleTon 
{ 
öffentlich: 
statisches SingleTon* getInstance() 
{ 
pthread_mutex_lock(&mutex); 
wenn(mpSingle == NULL) 
{ 
mpSingleTon = neuer SingleTon(); 
} 
pthread_mutex_unlock(&mutex); 
gibt mpSingleTon zurück; 
} 
Privat: 
SingleTon(){}; 
~SingleTon(){pthread_mutex_desttroy(&mutex,NULL);} 
statisches pthread_mutex_t-Mutex; 
statisches SingleTon *mpSingleTon; 
} 
pthread_mutex_t SingleTon::mutex = PTHREAD_MUTEX_INITIALIZER; 
SingleTon * SingleTon::mpSingleTon = NULL;

Vorteil:

Es besteht aus einem Speicherplatz (einer ausgerichteten Ganzzahlvariable), der von mehreren Prozessen gemeinsam genutzt werden kann. Der Wert dieser Ganzzahlvariable kann durch Aufrufen der von der CPU in Assemblersprache bereitgestellten atomaren Operationsanweisungen erhöht oder verringert werden, und ein Prozess kann warten, bis dieser Wert eine positive Zahl wird. Fast alle Vorgänge werden im Anwendungsbereich abgeschlossen. Nur wenn die Ergebnisse der Vorgänge inkonsistent sind und eine Arbitrierung erforderlich ist, muss zur Ausführung der Kernelbereich des Betriebssystems aufgerufen werden. Dieser Mechanismus ermöglicht die Verwendung von Sperrprimitiven mit sehr hoher Ausführungseffizienz: Da für die meisten Vorgänge keine Schlichtung zwischen mehreren Prozessen erforderlich ist, können die meisten Vorgänge im Anwendungsbereich ausgeführt werden, ohne (relativ teure) Kernel-Systemaufrufe zu verwenden.

2. Lese-/Schreibsperre

Funktionen: Lese-/Schreibsperren eignen sich für Situationen, in denen die Anzahl der Lesevorgänge einer Datenstruktur viel größer ist als die Anzahl der Schreibvorgänge. Da sie im Lesemodus gemeinsam genutzt werden kann und im Schreibmodus exklusiv ist, wird die Lese-/Schreibsperre auch als gemeinsam genutzte exklusive Sperre bezeichnet.

Initialisierung und Zerstörung:

int pthread_rwlock_init(pthread_rwlock_t *rwlock einschränken, const 
pthread_rwlockattr_t *attr einschränken); 
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

Gibt bei Erfolg 0 und bei einem Fehler eine Fehlernummer zurück. Wie beim Mutex müssen Sie vor der Freigabe des von der Lese-/Schreibsperre belegten Speichers die Lese-/Schreibsperre über pthread_rwlock_destroy bereinigen, um die von init zugewiesenen Ressourcen freizugeben.

Lesen und Schreiben:

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Bei Erfolg wird 0 zurückgegeben, bei einem Fehler eine Fehlernummer. Diese drei Funktionen implementieren die Operationen zum Erlangen einer Lesesperre, zum Erlangen einer Schreibsperre und zum Aufheben einer Sperre. Die beiden Funktionen zum Erlangen einer Sperre sind blockierende Operationen. Entsprechend sind die nicht blockierenden Funktionen:

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

Bei Erfolg wird 0 zurückgegeben, bei einem Fehler eine Fehlernummer. Bei nicht blockierenden Sperrenerlangungsvorgängen wird 0 zurückgegeben, wenn die Sperre erlangt werden kann, andernfalls wird der Fehler EBUSY zurückgegeben.

3. Drehsperre

Funktionen: Polling ist aktiv und wartet.

Auf einer Single-Core-CPU funktioniert es nicht: Der durch den Spinlock geschützte kritische Abschnittscode kann während der Ausführung nicht angehalten werden. Die ursprüngliche Absicht des Spinlocks, der einen Deadlock verursacht, besteht darin, in kurzer Zeit eine leichte Sperre durchzuführen. Eine umkämpfte Spinsperre führt dazu, dass der Thread, der sie anfordert, einen Spin ausführt, während er darauf wartet, dass die Sperre wieder verfügbar wird (was eine Verschwendung von Prozessorzeit darstellt). Spinsperren sollten daher nicht zu lange aufrechterhalten werden. Wenn die Sperre längere Zeit andauern muss, ist die Verwendung eines Semaphors besser.

API:

Zusammenfassen

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Lernwert für Ihr Studium oder Ihre Arbeit hat. Wenn Sie Fragen haben, können Sie eine Nachricht hinterlassen. Vielen Dank für Ihre Unterstützung von 123WORDPRESS.COM.

Das könnte Sie auch interessieren:
  • Definition von Array- und For-Loop-Traversal-Methoden in der Linux-Shell
  • Detailliertes Beispiel einer Array-Schleife in der Linux-Shell
  • Implementierungsmethode von Android zum Ausführen von Shell-Skripten im Linux-Terminal, um das Protokoll der aktuell ausgeführten App direkt auszudrucken
  • So führen Sie Linux-Befehlsprogramme remote und stapelweise mit pyqt aus
  • Verwendung der Zabbix-API in einer Linux-Shell-Umgebung
  • So stellen Sie versehentlich gelöschte Nachrichtendateien unter Linux wieder her
  • Detaillierte Analyse des Linux-Kernel-Makros container_of
  • Linux wird geladen, vmlinux wird debuggt
  • Zeigen Sie die Anzahl der Dateien in jedem Unterordner eines angegebenen Ordners in Linux an
  • Anwendungsbeispiele für Arrays und assoziative Arrays in der Linux-Shell

<<:  Detaillierte Erklärung zur Formatierung von Zahlen in MySQL

>>:  React implementiert den Beispielcode der Radiokomponente

Artikel empfehlen

So lösen Sie das Problem verschwommener kleiner Symbole auf Mobilgeräten

Vorwort Zuvor habe ich über das Problem der verti...

Eine kurze Analyse des Kimono-Memo-Problems

Heute musste ich nach dem Neustart des Spiels fes...

Uniapp implementiert Beispielcode für die Anmeldung mit DingTalk-Scancode

Da Uniapp nicht über eine autorisierte DingTalk-A...

MySQL-Transaktionsanalyse

Transaktion Eine Transaktion ist eine grundlegend...

DIV-Hintergrund, halbtransparenter Text, nicht durchscheinender Stil

Der DIV-Hintergrund ist halbtransparent, aber die ...

So aktualisieren Sie die Knotenversion unter CentOs manuell

1. Suchen Sie das entsprechende NodeJS-Paket unte...

Der eigentliche Prozess der Implementierung des Zahlenrätsels im WeChat-Applet

Inhaltsverzeichnis Funktionseinführung Rendern 1....

So verwenden Sie den Vue-Filter

Inhaltsverzeichnis Überblick Filter definieren Ve...

So installieren Sie PostgreSQL11 auf CentOS7

Installieren Sie PostgreSQL 11 auf CentOS 7 Postg...

So installieren Sie den MySQL 5.7.28-Binärmodus unter CentOS 7.4

Linux-Systemversion: CentOS7.4 MySQL-Version: 5.7...