Beispiel für Boost shared_mutex (mehrere Lesevorgänge / ein Schreibvorgang)?

116

Ich habe eine Multithread-App, die einige Daten häufig lesen muss, und gelegentlich werden diese Daten aktualisiert. Momentan schützt ein Mutex den Zugriff auf diese Daten sicher, aber es ist teuer, weil ich möchte, dass mehrere Threads gleichzeitig lesen können und sie nur dann sperren, wenn ein Update erforderlich ist (der Update-Thread könnte warten, bis die anderen Threads abgeschlossen sind). .

Ich denke, das boost::shared_mutexsoll das sein, aber ich weiß nicht genau, wie ich es verwenden soll, und habe kein klares Beispiel gefunden.

Hat jemand ein einfaches Beispiel, mit dem ich anfangen könnte?

kevin42
quelle
Das Beispiel von 1800 INFORMATION ist korrekt. Siehe auch diesen Artikel: Was in Boost - Threads ist neu .
Assaf Lavie
Mögliches Duplikat von Reader / Writer-Sperren in C ++
CHao

Antworten:

102

Es sieht so aus, als würden Sie so etwas tun:

boost::shared_mutex _access;
void reader()
{
  // get shared access
  boost::shared_lock<boost::shared_mutex> lock(_access);

  // now we have shared access
}

void writer()
{
  // get upgradable access
  boost::upgrade_lock<boost::shared_mutex> lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
  // now we have exclusive access
}
1800 INFORMATIONEN
quelle
7
Ich benutze Boost zum ersten Mal und bin ein C ++ - Neuling. Vielleicht fehlt mir etwas - aber in meinem eigenen Code musste ich den Typ wie folgt angeben: boost :: shared_lock <shared_mutex> lock (_Zugriff);
Ken Smith
2
Ich versuche dies selbst zu verwenden, erhalte jedoch eine Fehlermeldung. fehlende Vorlagenargumente vor 'sperren'. Irgendwelche Ideen?
Matt
2
@shaz Diese haben einen Gültigkeitsbereich, aber Sie können sie bei Bedarf frühzeitig mit .unlock () freigeben.
mmocny
4
Ich habe die fehlenden Vorlagenargumente hinzugefügt.
1
@raaj Sie können das upgrade_lock erhalten, aber das Upgrade auf ein eindeutiges Schloss wird blockiert, bis das shared_lock freigegeben wird
1800 INFORMATION
166

1800 INFORMATIONEN sind mehr oder weniger korrekt, aber es gibt einige Probleme, die ich korrigieren wollte.

boost::shared_mutex _access;
void reader()
{
  boost::shared_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access
}

void conditional_writer()
{
  boost::upgrade_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access

  if (something) {
    boost::upgrade_to_unique_lock< boost::shared_mutex > uniqueLock(lock);
    // do work here, but now you have exclusive access
  }

  // do more work here, without anyone having exclusive access
}

void unconditional_writer()
{
  boost::unique_lock< boost::shared_mutex > lock(_access);
  // do work here, with exclusive access
}

Beachten Sie außerdem, dass im Gegensatz zu einem shared_lock nur ein einzelner Thread gleichzeitig ein upgrade_lock erwerben kann, selbst wenn es nicht aktualisiert wurde (was ich als unangenehm empfand, als ich darauf stieß). Wenn alle Ihre Leser bedingte Autoren sind, müssen Sie eine andere Lösung finden.

mmocny
quelle
1
Nur um "eine andere Lösung" zu kommentieren. Wenn alle meine Leser bedingte Autoren waren, ließ ich sie immer ein shared_lock erwerben, und wenn ich ein Upgrade durchführen musste, um Privilegien zu schreiben, würde ich die Lesersperre .unlock () und ein neues unique_lock erwerben. Dies verkompliziert die Logik Ihrer Anwendung, und andere Autoren haben jetzt die Möglichkeit, den Status ab dem ersten Lesen zu ändern.
mmocny
8
Sollte nicht die Zeile boost::unique_lock< boost::shared_mutex > lock(lock);lesen boost::unique_lock< boost::shared_mutex > lock( _access ); ?
Steve Wilkinson
4
Diese letzte Einschränkung ist sehr seltsam. Wenn immer nur ein Thread gleichzeitig ein upgrade_lock enthalten kann, was ist der Unterschied zwischen einem upgrade_lock und einem unique_lock?
Ken Smith
2
@ Ken Ich war nicht sehr klar, aber der Vorteil von upgrade_lock ist, dass es nicht blockiert, wenn derzeit einige shared_locks erworben wurden (zumindest nicht, bis Sie ein Upgrade auf unique durchführen). Der zweite Thread, der versucht, ein upgrade_lock zu erwerben, wird jedoch blockiert, selbst wenn der erste nicht auf unique aktualisiert wurde, was ich nicht erwartet hatte.
mmocny
6
Dies ist ein bekanntes Boost-Problem. Es scheint bei Boost 1.50 Beta gelöst zu sein: svn.boost.org/trac/boost/ticket/5516
Ofek Shilon
47

Seit C ++ 17 (VS2015) können Sie den Standard für Lese- / Schreibsperren verwenden:

#include <shared_mutex>

typedef std::shared_mutex Lock;
typedef std::unique_lock< Lock > WriteLock;
typedef std::shared_lock< Lock > ReadLock;

Lock myLock;


void ReadFunction()
{
    ReadLock r_lock(myLock);
    //Do reader stuff
}

void WriteFunction()
{
     WriteLock w_lock(myLock);
     //Do writer stuff
}

Für ältere Versionen können Sie Boost mit derselben Syntax verwenden:

#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock >  WriteLock;
typedef boost::shared_lock< Lock >  ReadLock;
Yochai Timmer
quelle
5
Ich würde auch sagen typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
Reben
6
Sie müssen nicht die gesamte thread.hpp einschließen. Wenn Sie nur die Schlösser benötigen, schließen Sie die Schlösser ein. Es ist keine interne Implementierung. Halten Sie die Includes auf ein Minimum.
Yochai Timmer
5
Auf jeden Fall die einfachste Implementierung, aber ich finde es verwirrend, sowohl Mutexe als auch Sperren als Sperren zu bezeichnen. Ein Mutex ist ein Mutex, eine Sperre hält ihn in einem gesperrten Zustand.
Tim MB
17

Um weitere empirische Informationen hinzuzufügen, habe ich das gesamte Problem der aktualisierbaren Sperren und das Beispiel für den Boost shared_mutex (mehrere Lesevorgänge / ein Schreibvorgang) untersucht. ist eine gute Antwort, wenn Sie die wichtige Information hinzufügen, dass nur ein Thread ein upgrade_lock haben kann, auch wenn es nicht aktualisiert wird. Dies ist wichtig, da Sie kein Upgrade von einer gemeinsam genutzten Sperre auf eine eindeutige Sperre durchführen können, ohne zuvor die gemeinsam genutzte Sperre aufzuheben. (Dies wurde an anderer Stelle diskutiert, aber der interessanteste Thread ist hier http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Ich habe jedoch einen wichtigen (undokumentierten) Unterschied zwischen einem Thread gefunden, der auf ein Upgrade auf eine Sperre wartet (dh darauf warten muss, dass alle Leser die gemeinsam genutzte Sperre aufheben), und einer Schreibsperre, die auf dasselbe wartet (dh einer unique_lock).

  1. Der Thread, der auf einen unique_lock im shared_mutex wartet, blockiert alle neuen Leser, die hereinkommen. Sie müssen auf die Anforderung des Autors warten. Dies stellt sicher, dass Leser keine Schriftsteller verhungern (ich glaube jedoch, dass Schriftsteller Leser verhungern könnten).

  2. Der Thread, der darauf wartet, dass ein upgradeeable_lock aktualisiert wird, ermöglicht es anderen Threads, eine gemeinsame Sperre zu erhalten, sodass dieser Thread möglicherweise ausgehungert wird, wenn die Leser sehr häufig sind.

Dies ist ein wichtiges Thema, das zu berücksichtigen ist und wahrscheinlich dokumentiert werden sollte.

Jim Morris
quelle
3
Das Terekhov algorithmstellt sicher, dass 1.der Autor die Leser nicht verhungern kann. Sehen Sie das . Ist 2.aber wahr. Ein upgrade_lock garantiert keine Fairness. Sehen Sie das .
JonasVautherin
2

Verwenden Sie ein Semaphor mit einer Anzahl, die der Anzahl der Leser entspricht. Lassen Sie jeden Leser eine Zählung des Semaphors nehmen, um zu lesen, so dass alle gleichzeitig lesen können. Lassen Sie dann den Verfasser vor dem Schreiben ALLE Semaphorzählungen vornehmen. Dies führt dazu, dass der Writer wartet, bis alle Lesevorgänge abgeschlossen sind, und dann die Lesevorgänge beim Schreiben blockiert.

R Virzi
quelle
(1) Wie bringt man einen Schriftsteller dazu, die Zählung atomar um einen beliebigen Betrag zu verringern ? (2) Wenn der Schreiber die Zählung irgendwie auf Null verringert, wie wartet er dann darauf, dass bereits laufende Leser fertig sind, bevor er schreibt?
Ofek Shilon
Schlechte Idee: Wenn zwei Autoren gleichzeitig versuchen, darauf zuzugreifen, kann dies zu einem Deadlock führen.
Caduchon
2

Die großartige Antwort von Jim Morris, ich bin darauf gestoßen und es hat eine Weile gedauert, bis ich es herausgefunden habe. Hier ist ein einfacher Code, der zeigt, dass nach dem Senden einer "Anfrage" für einen unique_lock-Boost (Version 1.54) alle shared_lock-Anfragen blockiert werden. Dies ist sehr interessant, da es mir so scheint, als ob die Wahl zwischen unique_lock und upgradeeable_lock es erlaubt, wenn wir Schreibpriorität oder keine Priorität wünschen.

Auch (1) in Jim Morris 'Beitrag scheint dem zu widersprechen: Boost shared_lock. Bevorzugt lesen?

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

using namespace std;

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > UniqueLock;
typedef boost::shared_lock< Lock > SharedLock;

Lock tempLock;

void main2() {
    cout << "10" << endl;
    UniqueLock lock2(tempLock); // (2) queue for a unique lock
    cout << "11" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(1));
    lock2.unlock();
}

void main() {
    cout << "1" << endl;
    SharedLock lock1(tempLock); // (1) aquire a shared lock
    cout << "2" << endl;
    boost::thread tempThread(main2);
    cout << "3" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(3));
    cout << "4" << endl;
    SharedLock lock3(tempLock); // (3) try getting antoher shared lock, deadlock here
    cout << "5" << endl;
    lock1.unlock();
    lock3.unlock();
}
dale1209
quelle
Ich habe tatsächlich Probleme herauszufinden, warum der obige Code blockiert, während der Code in [ stackoverflow.com/questions/12082405/… funktioniert.
Dale1209
1
Es blockiert tatsächlich in (2), nicht in (3), weil (2) darauf wartet, dass (1) seine Sperre aufhebt. Denken Sie daran: Um eine eindeutige Sperre zu erhalten, müssen Sie warten, bis alle vorhandenen freigegebenen Sperren abgeschlossen sind.
JonasVautherin
@JonesV, selbst wenn (2) darauf wartet, dass alle gemeinsam genutzten Sperren abgeschlossen sind, wäre dies kein Deadlock, da es sich um einen anderen Thread handelt als den, der (1) erworben hat. Wenn Zeile (3) nicht vorhanden wäre, würde das Programm dies tun Beenden Sie ohne Deadlocks.
SagiLow