views:

1334

answers:

8

I have to implement a read/write lock in C++ using the Win32 api as part of a project at work. All of the existing solutions use kernel objects (semaphores and mutexes) that require a context switch during execution. This is far too slow for my application.

I would like implement one using only critical sections, if possible. The lock does not have to be process safe, only threadsafe. Any ideas on how to go about this?

A: 

If you already know of a solution that only uses mutexes, you should be able to modify it to use critical sections instead.

We rolled our own using two critical sections and some counters. It suits our needs - we have a very low writer count, writers get precedence over readers, etc. I'm not at liberty to publish ours but can say that it is possible without mutexes and semaphores.

sean e
The problem is that with a standard mutex, anyone can lock/unlock it. This isn't true for a critical section, which makes my solution not work.
Dan Lorenc
Not clear what you mean - only the thread that owns a mutext can unlock it. Once a mutex is unlocked, anyone can lock it. Likewise with a critical section.
sean e
Sorry, I meant a semaphore. I need a solution where anyone can decrement the semaphore, which isn't possible with critical sections.
Dan Lorenc
Depending on what the semaphore is used for, you might be able to use a counter that is modified via InterlockedIncrement/InterlockedDecrement.
sean e
A: 

Yep it's pretty straightforward

CRITICAL_SECTION cs; // probably best to store this in a singleton class somewhere or make it static

// setup 
InitializeCriticalSection(&cs); // best to setup at start of program

void SomeFunc()
{
EnterCriticalSection(&cs);
 // do whatever you need to do here
LeaveCriticalSection(&cs);
}

Remember to call DeleteCriticalSection on exit of the program

zebrabox
I assumed he wanted a multiple readers / single writer locking solution...
sean e
I don't think he specified what he wanted
zebrabox
Yep - bad post from me. Apologies. Could a mod delete this please?
zebrabox
+3  A: 

Take a look at the book "Concurrent Programming on Windows" which has lots of different reference examples for reader/writer locks.

Paul Alexander
+3  A: 

Check out the spin_rw_mutex from Intel's Thread Building Blocks ...

spin_rw_mutex is strictly in user-land and employs spin-wait for blocking

JP Alioto
Good link - the article notes that Vista has a built-in rw lock http://msdn.microsoft.com/en-us/library/aa904937(VS.85).aspx
sean e
+5  A: 

If you can target Vista or greater, you should use the built-in SRWLock's. They are lightweight like critical sections, entirely user-mode when there is no contention.

Joe Duffy's blog has some recent entries on implementing different types of non-blocking reader/writer locks. These locks do spin, so they would not be appropriate if you intend to do a lot of work while holding the lock. The code is C#, but should be straightforward to port to native.

You can implement a reader/writer lock using critical sections and events - you just need to keep enough state to only signal the event when necessary to avoid an unnecessary kernel mode call.

Michael
+4  A: 

I don't think this can be done without using at least one kernel-level object (Mutex or Semaphore), because you need the help of the kernel to make the calling process block until the lock is available.

Critical sections do provide blocking, but the API is too limited. e.g. you cannot grab a CS, discover that a read lock is available but not a write lock, and wait for the other process to finish reading (because if the other process has the critical section it will block other readers which is wrong, and if it doesn't then your process will not block but spin, burning CPU cycles.)

However what you can do is use a spin lock and fall back to a mutex whenever there is contention. The critical section is itself implemented this way. I would take an existing critical section implementation and replace the PID field with separate reader & writer counts.

finnw
A: 

Here is the smallest solution that I could come up with:

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

And pasted verbatim:

/** 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 );
    }
};
Ben Harper
A: 

Old question, but this is something that should work. It doesn't spin on contention. OTOH it is a bit heavy-weight if readers have little or no contention because they incur extra cost for the ResetEvent and SetEvent system calls.

#include <windows.h>

typedef struct _RW_LOCK {
    CRITICAL_SECTION readerCountLock;
    CRITICAL_SECTION writerLock;
    HANDLE noReaders;
    int readerCount;
} RW_LOCK, *PRW_LOCK;

void rwlock_init(PRW_LOCK rwlock)
{
    InitializeCriticalSection(&rwlock->readerCountLock);
    InitializeCriticalSection(&rwlock->writerLock);

    /*
     * We use a manual-reset event as poor man condition variable that
     * can only do broadcast.  Actually, only one thread will be waiting
     * on this at any time, because the wait is done while holding the
     * writerLock.
     */
    rwlock->noReaders = CreateEvent (NULL, TRUE, TRUE, 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->readerCountLock);
    if (++rwlock->readerCount == 1) {
        ResetEvent(rwlock->noReaders);
    }
    LeaveCriticalSection(&rwlock->readerCountLock);
    LeaveCriticalSection(&rwlock->writerLock);
}

int rwlock_wrlock(PRW_LOCK rwlock)
{
    int owned;

    EnterCriticalSection(&rwlock->writerLock);
    if (rwlock->readerCount > 0) {
        WaitForSingleObject(rwlock->noReaders, INFINITE);
    }

    /* writerLock remains locked.  */
}

void rwlock_rdunlock(PRW_LOCK rwlock)
{
    EnterCriticalSection(&rwlock->readerCountLock);
    assert (rwlock->readerCount > 0);
    if (--rwlock->readerCount == 0) {
        SetEvent(rwlock->noReaders);
    }
    LeaveCriticalSection(&rwlock->readerCountLock);
}

void rwlock_wrunlock(PRW_LOCK rwlock)
{
    LeaveCriticalSection(&rwlock->writerLock);
}
Paolo Bonzini