vecchia questione, ma questo è qualcosa che dovrebbe funzionare. Non gira su contesa. I lettori possono sostenere costi aggiuntivi limitati se hanno poca o nessuna contesa, perché SetEvent
è chiamato pigramente (guarda la cronologia delle modifiche per una versione più pesante che non ha questa ottimizzazione).
#include <windows.h>
typedef struct _RW_LOCK {
CRITICAL_SECTION countsLock;
CRITICAL_SECTION writerLock;
HANDLE noReaders;
int readerCount;
BOOL waitingWriter;
} RW_LOCK, *PRW_LOCK;
void rwlock_init(PRW_LOCK rwlock)
{
InitializeCriticalSection(&rwlock->writerLock);
InitializeCriticalSection(&rwlock->countsLock);
/*
* Could use a semaphore as well. There can only be one waiter ever,
* so I'm showing an auto-reset event here.
*/
rwlock->noReaders = CreateEvent (NULL, FALSE, FALSE, NULL);
}
void rwlock_rdlock(PRW_LOCK rwlock)
{
/*
* We need to lock the writerLock too, otherwise a writer could
* do the whole of rwlock_wrlock after the readerCount changed
* from 0 to 1, but before the event was reset.
*/
EnterCriticalSection(&rwlock->writerLock);
EnterCriticalSection(&rwlock->countsLock);
++rwlock->readerCount;
LeaveCriticalSection(&rwlock->countsLock);
LeaveCriticalSection(&rwlock->writerLock);
}
int rwlock_wrlock(PRW_LOCK rwlock)
{
EnterCriticalSection(&rwlock->writerLock);
/*
* readerCount cannot become non-zero within the writerLock CS,
* but it can become zero...
*/
if (rwlock->readerCount > 0) {
EnterCriticalSection(&rwlock->countsLock);
/* ... so test it again. */
if (rwlock->readerCount > 0) {
rwlock->waitingWriter = TRUE;
LeaveCriticalSection(&rwlock->countsLock);
WaitForSingleObject(rwlock->noReaders, INFINITE);
} else {
/* How lucky, no need to wait. */
LeaveCriticalSection(&rwlock->countsLock);
}
}
/* writerLock remains locked. */
}
void rwlock_rdunlock(PRW_LOCK rwlock)
{
EnterCriticalSection(&rwlock->countsLock);
assert (rwlock->readerCount > 0);
if (--rwlock->readerCount == 0) {
if (rwlock->waitingWriter) {
/*
* Clear waitingWriter here to avoid taking countsLock
* again in wrlock.
*/
rwlock->waitingWriter = FALSE;
SetEvent(rwlock->noReaders);
}
}
LeaveCriticalSection(&rwlock->countsLock);
}
void rwlock_wrunlock(PRW_LOCK rwlock)
{
LeaveCriticalSection(&rwlock->writerLock);
}
Si potrebbe diminuire il costo per i lettori, utilizzando un unico CRITICAL_SECTION
:
countsLock
è sostituito con writerLock
in rdlock e rdunlock
rwlock->waitingWriter = FALSE
viene rimosso in wrunlock
Il corpo del wrlock è cambiato in
EnterCriticalSection(&rwlock->writerLock);
rwlock->waitingWriter = TRUE;
while (rwlock->readerCount > 0) {
LeaveCriticalSection(&rwlock->writerLock);
WaitForSingleObject(rwlock->noReaders, INFINITE);
EnterCriticalSection(&rwlock->writerLock);
}
rwlock->waitingWriter = FALSE;
/* writerLock remains locked. */
Tuttavia, questo perde in tutta onestà, quindi preferisco la soluzione di cui sopra.
fonte
2010-10-18 14:47:43
Il problema è che con un mutex standard, chiunque può bloccarlo/sbloccarlo. Questo non è vero per una sezione critica, il che rende la mia soluzione non funzionante. –
Non chiaro cosa intendi: solo il thread che possiede un mutext può sbloccarlo. Una volta sbloccato un mutex, chiunque può bloccarlo. Allo stesso modo con una sezione critica. –
Scusa, intendevo un semaforo. Ho bisogno di una soluzione in cui chiunque possa decrementare il semaforo, cosa non possibile con le sezioni critiche. –