Question

Je dois implémenter un verrou en lecture / écriture en C ++ en utilisant l'API Win32 dans le cadre d'un projet au travail. Toutes les solutions existantes utilisent des objets de noyau (sémaphores et mutex) nécessitant un commutateur de contexte lors de l'exécution. C'est beaucoup trop lent pour mon application.

J'aimerais en implémenter une en n'utilisant que des sections critiques, si possible. Le verrou ne doit pas nécessairement être sûr du processus, seulement threadsafe. Des idées sur la façon de s'y prendre?

Était-ce utile?

La solution

Je ne pense pas que cela puisse être fait sans utiliser au moins un objet de niveau noyau (Mutex ou Sémaphore), car vous avez besoin de l'aide du noyau pour bloquer le processus appelant jusqu'à ce que le verrou soit disponible.

Les sections critiques fournissent un blocage, mais l’API est trop limitée. par exemple. vous ne pouvez pas récupérer un CS, découvrir qu'un verrou de lecture est disponible mais pas un verrou d'écriture, et attendre que l'autre processus ait fini de lire (car si l'autre processus contient la section critique, il bloquera les autres lecteurs, ce qui est faux et s'il alors votre processus ne bloquera pas mais tournera, brûlant des cycles du processeur.)

Cependant, vous pouvez utiliser un verrou de rotation et retomber sur un mutex chaque fois qu’il ya conflit. La section critique est elle-même mise en œuvre de cette façon. Je prendrais une implémentation de section critique existante et remplacerais le champ PID par un lecteur séparé & amp; l'écrivain compte.

Autres conseils

Si vous pouvez cibler Vista ou une version ultérieure, vous devez utiliser le fichier SRWLock . Ils sont légers comme des sections critiques, entièrement en mode utilisateur lorsqu'il n'y a pas de conflit.

Le blog de Joe Duffy contient des entrées sur la mise en œuvre différente types de verrous lecteur / graveur non bloquants. Ces serrures tournent, elles ne seraient donc pas appropriées si vous avez l’intention de faire beaucoup de travail en tenant la serrure. Le code est C #, mais devrait être simple à porter en natif.

Vous pouvez implémenter un verrou lecteur / graveur à l’aide de sections et d’événements critiques.

Vieille question, mais c'est quelque chose qui devrait fonctionner. Il ne tourne pas sur la controverse. Les lecteurs supportent des frais supplémentaires limités s'ils ont peu ou pas de conflit, car SetEvent est appelé paresseusement (consultez l'historique de modification pour une version plus lourde ne disposant pas de cette optimisation).

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

Vous pouvez réduire le coût pour les lecteurs en utilisant un seul CRITICAL_SECTION :

.
  • countLock est remplacé par writerLock dans rdlock et rdunlock

  • rwlock- > waitingWriter = FALSE est supprimé de wrunlock

  • le corps de wrlock est changé en

    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.  */
    

Cependant, cela perd en justice, je préfère donc la solution ci-dessus.

Consultez le livre " Programmation simultanée sous Windows ". ; qui contient de nombreux exemples de référence pour les verrous lecteur / graveur.

Découvrez le spin_rw_mutex des blocs de construction de threads ...

  

spin_rw_mutex est strictement dans le pays utilisateur   et utilise spin-wait pour le blocage

Ceci est une vieille question mais peut-être que quelqu'un trouvera cela utile. Nous avons développé un RWLock à code source ouvert pour Windows qui fonctionne automatiquement. utilise Vista + SRWLock Michael a mentionné s'il est disponible, ou recule autrement vers une implémentation en espace utilisateur.

En prime, il existe quatre "variantes" différentes. (bien que vous puissiez vous en tenir à la base, qui est aussi la plus rapide), chacune offrant davantage d’options de synchronisation. Cela commence par le RWLock () de base, non réentrant, limité à la synchronisation avec un seul processus et sans permutation des verrous en lecture / écriture sur un IPC RWLock avec traitement croisé à part entière avec ré-entrée assistance et lecture / écriture de la suppression.

Comme cela a été mentionné, ils basculent de manière dynamique vers les verrous de lecture-écriture minces Vista + pour obtenir les meilleures performances possibles, mais vous n'avez pas à vous en préoccuper, car cela impliquera une implémentation entièrement compatible sous Windows. XP et ses semblables.

Si vous connaissez déjà une solution qui uniquement utilise des mutex, vous devriez pouvoir le modifier pour utiliser à la place des sections critiques.

Nous avons lancé notre propre en utilisant deux sections critiques et quelques compteurs. Cela correspond à nos besoins - nous comptons très peu d'écrivains, ceux-ci ont la priorité sur les lecteurs, etc. Je ne suis pas libre de publier les nôtres, mais je peux dire que c'est possible sans mutex ni sémaphores.

Voici la plus petite solution que je pourrais trouver:

http://www.baboonz.org/rwlock.php

Et collé textuellement:

/** A simple Reader/Writer Lock.

This RWL has no events - we rely solely on spinlocks and sleep() to yield control to other threads.
I don't know what the exact penalty is for using sleep vs events, but at least when there is no contention, we are basically
as fast as a critical section. This code is written for Windows, but it should be trivial to find the appropriate
equivalents on another OS.

**/
class TinyReaderWriterLock
{
public:
    volatile uint32 Main;
    static const uint32 WriteDesireBit = 0x80000000;

    void Noop( uint32 tick )
    {
        if ( ((tick + 1) & 0xfff) == 0 )     // Sleep after 4k cycles. Crude, but usually better than spinning indefinitely.
            Sleep(0);
    }

    TinyReaderWriterLock()                 { Main = 0; }
    ~TinyReaderWriterLock()                { ASSERT( Main == 0 ); }

    void EnterRead()
    {
        for ( uint32 tick = 0 ;; tick++ )
        {
            uint32 oldVal = Main;
            if ( (oldVal & WriteDesireBit) == 0 )
            {
                if ( InterlockedCompareExchange( (LONG*) &Main, oldVal + 1, oldVal ) == oldVal )
                    break;
            }
            Noop(tick);
        }
    }

    void EnterWrite()
    {
        for ( uint32 tick = 0 ;; tick++ )
        {
            if ( (tick & 0xfff) == 0 )                                     // Set the write-desire bit every 4k cycles (including cycle 0).
                _InterlockedOr( (LONG*) &Main, WriteDesireBit );

            uint32 oldVal = Main;
            if ( oldVal == WriteDesireBit )
            {
                if ( InterlockedCompareExchange( (LONG*) &Main, -1, WriteDesireBit ) == WriteDesireBit )
                    break;
            }
            Noop(tick);
        }
    }

    void LeaveRead()
    {
        ASSERT( Main != -1 );
        InterlockedDecrement( (LONG*) &Main );
    }
    void LeaveWrite()
    {
        ASSERT( Main == -1 );
        InterlockedIncrement( (LONG*) &Main );
    }
};

Regardez ma mise en œuvre ici:

https://github.com/coolsoftware/LockLib

VRWLock est une classe C ++ qui implémente la logique d'écriture unique - lecteurs multiples.

Regardez également le projet test TestLock.sln.

UPD. Voici le code simple pour lecteur et écrivain:

LONG gCounter = 0;

// reader

for (;;) //loop
{
  LONG n = InterlockedIncrement(&gCounter); 
  // n = value of gCounter after increment
  if (n <= MAX_READERS) break; // writer does not write anything - we can read
  InterlockedDecrement(&gCounter);
}
// read data here
InterlockedDecrement(&gCounter); // release reader

// writer

for (;;) //loop
{
  LONG n = InterlockedCompareExchange(&gCounter, (MAX_READERS+1), 0); 
  // n = value of gCounter before attempt to replace it by MAX_READERS+1 in InterlockedCompareExchange
  // if gCounter was 0 - no readers/writers and in gCounter will be MAX_READERS+1
  // if gCounter was not 0 - gCounter stays unchanged
  if (n == 0) break;
}
// write data here
InterlockedExchangeAdd(&gCounter, -(MAX_READERS+1)); // release writer

La classe VRWLock prend en charge le nombre de spin et le nombre de références spécifiques aux threads qui permettent de libérer les verrous des threads terminés.

J'ai écrit le code suivant en n'utilisant que des sections critiques.

class ReadWriteLock {
    volatile LONG writelockcount;
    volatile LONG readlockcount;
    CRITICAL_SECTION cs;
public:
    ReadWriteLock() {
        InitializeCriticalSection(&cs);
        writelockcount = 0;
        readlockcount = 0;
    }
    ~ReadWriteLock() {
        DeleteCriticalSection(&cs);
    }
    void AcquireReaderLock() {        
    retry:
        while (writelockcount) {
            Sleep(0);
        }
        EnterCriticalSection(&cs);
        if (!writelockcount) {
            readlockcount++;
        }
        else {
            LeaveCriticalSection(&cs);
            goto retry;
        }
        LeaveCriticalSection(&cs);
    }
    void ReleaseReaderLock() {
        EnterCriticalSection(&cs);
        readlockcount--;
        LeaveCriticalSection(&cs);
    }
    void AcquireWriterLock() {
        retry:
        while (writelockcount||readlockcount) {
            Sleep(0);
        }
        EnterCriticalSection(&cs);
        if (!writelockcount&&!readlockcount) {
            writelockcount++;
        }
        else {
            LeaveCriticalSection(&cs);
            goto retry;
        }
        LeaveCriticalSection(&cs);
    }
    void ReleaseWriterLock() {
        EnterCriticalSection(&cs);
        writelockcount--;
        LeaveCriticalSection(&cs);
    }
};

Pour effectuer une attente en attente, commentez les lignes avec Sleep (0).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top