Esempio per boost shared_mutex (più letture / una scrittura)?


116

Ho un'app multithread che deve leggere spesso alcuni dati e occasionalmente tali dati vengono aggiornati. In questo momento un mutex mantiene l'accesso a quei dati al sicuro, ma è costoso perché vorrei che più thread fossero in grado di leggere contemporaneamente e bloccarli solo quando è necessario un aggiornamento (il thread di aggiornamento potrebbe attendere che gli altri thread finiscano) .

Penso che questo sia ciò che boost::shared_mutexdovrebbe fare, ma non sono chiaro su come usarlo e non ho trovato un esempio chiaro.

Qualcuno ha un semplice esempio che potrei usare per iniziare?


L'esempio di 1800 INFORMAZIONI è corretto. Vedi anche questo articolo: Novità di Boost Threads .
Assaf Lavie

possibile duplicato di Reader / Writer Locks in C ++
cHao

Risposte:


102

Sembra che faresti qualcosa del genere:

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
}

7
Questa è la prima volta che utilizzo boost e sono un principiante di C ++, quindi forse mi manca qualcosa, ma nel mio codice ho dovuto specificare il tipo, in questo modo: boost :: shared_lock <shared_mutex> lock (_accesso);
Ken Smith

2
Sto cercando di usarlo da solo ma ricevo un errore. argomenti del modello mancanti prima di "lock". Qualche idea?
Matt

2
@shaz Questi sono con scope, ma puoi rilasciarli in anticipo con .unlock () se necessario.
mmocny

4
Ho aggiunto gli argomenti del modello mancanti.

1
@raaj puoi ottenere l'aggiornamento_lock, ma l'aggiornamento a un blocco univoco si bloccherà fino al rilascio di shared_lock
1800 INFORMAZIONI

166

1800 INFORMAZIONI è più o meno corretto, ma ci sono alcuni problemi che volevo correggere.

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
}

Nota inoltre, a differenza di shared_lock, solo un singolo thread può acquisire un upgrade_lock in una sola volta, anche quando non è aggiornato (cosa che ho pensato fosse imbarazzante quando mi sono imbattuto in esso). Quindi, se tutti i tuoi lettori sono scrittori condizionali, devi trovare un'altra soluzione.


1
Giusto per commentare "un'altra soluzione". Quando tutti i miei lettori erano scrittori condizionali, quello che facevo era che acquisissero sempre un shared_lock, e quando avevo bisogno di aggiornare per scrivere i privilegi, avrei bloccato il lettore .unlock () e acquisivo un nuovo unique_lock. Ciò complicherà la logica della tua applicazione e ora c'è una finestra di opportunità per altri autori di cambiare lo stato dalla prima volta che hai letto.
mmocny

8
La riga non dovrebbe boost::unique_lock< boost::shared_mutex > lock(lock);leggere boost::unique_lock< boost::shared_mutex > lock( _access ); ?
SteveWilkinson

4
Quest'ultimo avvertimento è molto strano. Se solo un thread può contenere un upgrade_lock alla volta, qual è la differenza tra un upgrade_lock e un unique_lock?
Ken Smith

2
@Ken Non sono stato molto chiaro, ma il vantaggio di upgrade_lock è che non si blocca se ci sono attualmente alcuni shared_lock acquisiti (almeno non fino a quando non esegui l'aggiornamento a unique). Tuttavia, il secondo thread per provare ad acquisire un upgrade_lock si bloccherà, anche se il primo non è stato aggiornato a univoco, cosa che non mi aspettavo.
mmocny

6
Questo è un problema di boost noto. Sembra essere risolto al boost 1.50 beta: svn.boost.org/trac/boost/ticket/5516
Ofek Shilon

47

A partire da C ++ 17 (VS2015) è possibile utilizzare lo standard per i blocchi di lettura-scrittura:

#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
}

Per la versione precedente, puoi utilizzare boost con la stessa sintassi:

#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;

5
Direi anche typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
vigne

6
Non è necessario includere l'intero thread.hpp. Se hai solo bisogno dei lucchetti, includi i lucchetti. Non è un'implementazione interna. Mantieni le inclusioni al minimo.
Yochai Timmer

5
Sicuramente l'implementazione più semplice, ma penso che sia fonte di confusione fare riferimento a mutex e lock come Lock. Un mutex è un mutex, un lock è qualcosa che lo mantiene in uno stato bloccato.
Tim MB

17

Solo per aggiungere alcune informazioni più empiriche, ho indagato sull'intero problema dei blocchi aggiornabili e Esempio per boost shared_mutex (più letture / una scrittura)? è una buona risposta aggiungendo le informazioni importanti che solo un thread può avere un upgrade_lock anche se non è aggiornato, che è importante in quanto significa che non è possibile aggiornare da un blocco condiviso a un blocco univoco senza rilasciare prima il blocco condiviso. (Questo è stato discusso altrove, ma il thread più interessante è qui http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Tuttavia ho trovato un'importante differenza (non documentata) tra un thread in attesa di un aggiornamento a un blocco (cioè deve attendere che tutti i lettori rilascino il blocco condiviso) e un blocco del writer in attesa della stessa cosa (cioè un unique_lock).

  1. Il thread in attesa di un unique_lock su shared_mutex blocca eventuali nuovi lettori in arrivo, devono attendere la richiesta dei writer. Ciò garantisce che i lettori non muoiano di fame gli scrittori (tuttavia credo che gli scrittori potrebbero far morire di fame i lettori).

  2. Il thread in attesa dell'aggiornamento di un upgradeeable_lock consente ad altri thread di ottenere un blocco condiviso, quindi questo thread potrebbe essere affamato se i lettori sono molto frequenti.

Questa è una questione importante da considerare e probabilmente dovrebbe essere documentata.


3
Le Terekhov algorithmassicura che nel 1., lo scrittore non possono morire di fame i lettori. Vedi questo . Ma 2.è vero. Un upgrade_lock non garantisce l'equità. Vedi questo .
JonasVautherin

2

Utilizza un semaforo con un conteggio uguale al numero di lettori. Lascia che ogni lettore prenda un conteggio del semaforo per leggere, in questo modo possono leggere tutti allo stesso tempo. Quindi lascia che lo scrittore prenda TUTTI i conteggi dei semafori prima di scrivere. Ciò fa sì che il writer attenda il completamento di tutte le letture e quindi blocchi le letture durante la scrittura.


(1) Come si fa a fare in modo che uno scrittore decrementi atomicamente il conteggio di un importo arbitrario ? (2) Se lo scrittore in qualche modo decrementa il conteggio a zero, come attende che i lettori già in esecuzione finiscano prima di scrivere?
Ofek Shilon

Cattiva idea: se due autori tentano di accedere contemporaneamente, è possibile che si verifichi un deadlock.
Caduchon

2

Grande risposta di Jim Morris, mi sono imbattuto in questo e mi ci è voluto un po 'per capire. Ecco un semplice codice che mostra che dopo aver inviato una "richiesta" per un unique_lock boost (versione 1.54) blocca tutte le richieste shared_lock. Questo è molto interessante in quanto mi sembra che scegliere tra unique_lock e upgradeable_lock consenta se vogliamo priorità di scrittura o nessuna priorità.

Anche (1) nel post di Jim Morris sembra contraddire questo: Boost shared_lock. Leggere preferito?

#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();
}

In realtà ho problemi a capire perché il codice precedente si blocca mentre il codice in [ stackoverflow.com/questions/12082405/… funziona.
dale1209

1
In realtà si blocca in (2), non in (3), perché (2) è in attesa che (1) rilasci il suo blocco. Ricorda: per ottenere un blocco univoco, è necessario attendere che tutti i blocchi condivisi esistenti finiscano.
JonasVautherin

@JonesV, anche se (2) aspetta che tutti i blocchi condivisi finiscano, non sarebbe un deadlock perché è un thread diverso da quello che ha acquisito (1), se la riga (3) non esistesse, il programma lo farebbe finire senza deadlock.
SagiLow
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.