2016-02-23 46 views
5

Sto cercando di usare di lettura/scrittura di blocco in C++ usando shared_mutexLeggi Scrivi implementazione di blocco in C++

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

class Test { 
    Lock lock; 
    WriteLock writeLock; 
    ReadLock readLock; 

    Test() : writeLock(lock), readLock(lock) {} 

    readFn1() { 
     readLock.lock(); 
     /* 
      Some Code 
     */ 
     readLock.unlock(); 
    } 

    readFn2() { 
     readLock.lock(); 
     /* 
      Some Code 
     */ 
     readLock.unlock(); 
    } 

    writeFn1() { 
     writeLock.lock(); 
     /* 
      Some Code 
     */ 
     writeLock.unlock(); 
    } 

    writeFn2() { 
     writeLock.lock(); 
     /* 
      Some Code 
     */ 
     writeLock.unlock(); 
    } 
} 

Il codice sembra funzionare bene, ma ho alcune domande concettuali.

Q1. Ho visto i consigli per utilizzare unique_lock e shared_lock su http://en.cppreference.com/w/cpp/thread/shared_mutex/lock, ma non capisco perché perché shared_mutex supporta già i metodi lock e lock_shared?

Q2. Questo codice ha il potenziale per causare la fame di scrittura? Se sì, allora come posso evitare la fame?

Q3. C'è qualche altra classe di blocco che posso provare ad implementare il blocco di scrittura?

+1

Se si osserva la mancanza di digiuno (o in caso di inedia), presentare una segnalazione di errore. Esistono algoritmi che assicurano che la fame non avvenga, e questo è il motivo per cui non vi è alcuna API di priorità reader/writer in questo codice. Vedi http://stackoverflow.com/a/14307116/576911 per i collegamenti a tali algoritmi. Ecco un link a un articolo con motivazione e un'implementazione parziale: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html#shared_mutex_imp –

risposta

2

Q1: utilizzo di un involucro mutex

La raccomandazione di usare un oggetto wrapper invece di gestire direttamente il mutex è quello di evitare spiacevole situazione in cui il codice viene interrotta e il mutex non viene rilasciato, lasciandolo bloccato per sempre.

Questo è il principio di RAII.

Ma questo funziona solo se ReadLock o WriteLock sono locali alla funzione che lo utilizza.

Esempio:

readFn1() { 
    boost::unique_lock<Lock> rl(lock); 
    /* 
     Some Code 
     ==> imagine exception is thrown 
    */ 
    rl.unlock(); // this is never reached if exception thrown 
} // fortunately local object are destroyed automatically in case 
    // an excpetion makes you leave the function prematurely  

Nel codice questo non funzionerà se una delle funzioni è interupted, becaus l'oggetto ReadLock WriteLock è un utente di prova e non locale alla impostazione del blocco di funzione.

Q2: Scrivere fame

Non è del tutto chiaro come si intende richiamare i lettori e gli scrittori, ma sì, c'è un rischio:

  • fintanto che i lettori sono attivi, lo scrittore è bloccato da unique_lock in attesa che il mutex divenga appetibile in modalità esclusiva.
  • tuttavia, fino a quando il wrurst è in attesa, i nuovi lettori possono ottenere l'accesso al blocco condiviso, causando un ulteriore ritardo del blocco unique_lock.

Se si desidera evitare la fame, è necessario assicurarsi che gli scrittori in attesa ottengano l'opportunità di impostare il proprio unique_lock. Ad esempio, attacca nei tuoi lettori un codice per verificare se uno scrittore è in attesa prima di impostare il blocco.

Q3 Altre classi di bloccaggio

Non abbastanza sicuro di quello che stai cercando, ma ho l'impressione che condition_variable potrebbe essere di interesse per voi. Ma la logica è un po 'diversa.

Forse, potresti anche trovare una soluzione pensando fuori dagli schemi: forse c'è una struttura di dati idonea al lock-free che potrebbe facilitare la coesistenza di lettori e scrittori cambiando leggermente l'approccio?

2

I tipi per i blocchi sono ok ma invece di averli come funzioni membro creano quindi all'interno delle funzioni membro locktype lock(mymutex). In questo modo vengono rilasciati alla distruzione anche nel caso di un'eccezione.

+0

dovrebbe leggere ... come membri, le funzioni li creano .. ecc. – systemcpro

+0

ok. Ho arruffato quel lol. Quindi proverò a scriverlo in inglese :) – systemcpro

+0

All'interno di ogni funzione membro crei un'istanza del tipo di blocco desiderato sullo stack 'shared_lock (mylock)'. Non è necessario rilasciarlo, viene rilasciato quando esce dall'ambito all'uscita dalla funzione. Funziona anche nel caso di eccezioni.Spero che sia un po 'più come un inglese appropriato. È stata una lunga giornata :( – systemcpro

1

Q1. Ho visto i consigli per utilizzare unique_lock e shared_lock su http://en.cppreference.com/w/cpp/thread/shared_mutex/lock, ma non capisco perché perché shared_mutex supporta già i metodi lock e lock_shared?

Forse perché unique_lock è presente da C++ 11 ma shared_lock è in arrivo con C++ 17. Inoltre, [eventualmente] unique_lock può essere più efficiente. Ecco la logica originale per shared_lock [del creatore] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html e io rimando a ciò.

Q2. Questo codice ha il potenziale per causare la fame di scrittura? Se sì, allora come posso evitare la fame?

Sì, assolutamente. Se lo fai:

while (1) 
    writeFn1(); 

si può finire con una linea del tempo di:

T1: writeLock.lock() 
T2: writeLock.unlock() 

T3: writeLock.lock() 
T4: writeLock.unlock() 

T5: writeLock.lock() 
T6: writeLock.unlock() 

... 

La differenza T2-T1 è arbitraria, sulla base di quantità di lavoro svolto. Ma, T3-T2 è vicino allo zero. Questa è la finestra per un altro thread per acquisire il blocco. Poiché la finestra è così piccola, probabilmente non la otterrà.

Per risolvere questo problema, il metodo più semplice consiste nell'inserire una piccola sospensione (ad esempio nanosleep) tra T2 e T3. Puoi farlo aggiungendolo alla base di writeFn1.

Altri metodi possono comportare la creazione di una coda per il blocco. Se un thread non riesce a ottenere il blocco, si aggiunge a una coda e il primo thread nella coda ottiene il blocco quando viene rilasciato il blocco. Nel kernel linux, questo è implementato per uno "spinlock in coda"

Q3. C'è qualche altra classe di blocco che posso provare ad implementare il blocco di scrittura?

Anche se non è una classe, è possibile utilizzare pthread_mutex_lock e pthread_mutex_unlock. Questi implementano serrature ricorsive. È possibile aggiungere il proprio codice per implementare l'equivalente di boost::scoped_lock. La tua classe può controllare la semantica.

Oppure, boost ha le proprie serrature.