Win32 Блокировка Чтения / Записи С Использованием Только Критических Разделов

Я должен реализовать блокировку чтения/записи в C++, используя Win32 api как часть проекта на работе. Все существующие решения используют объекты ядра (семафоры и мьютексы), которые требуют переключения контекста во время выполнения. Это слишком медленно для моего приложения.

Я хотел бы реализовать один, используя только критические разделы, если это возможно. Замок не должен быть отростчатым сейфом, только threadsafe. Есть идеи, как это сделать?

10 ответов


Я не думаю, что это можно сделать, не используя хотя бы один объект уровня ядра (мьютекс или семафор), потому что вам нужна помощь ядра, чтобы сделать блок вызывающего процесса, пока блокировка не будет доступна.

критические разделы обеспечивают блокировку, но API слишком ограничен. например, вы не можете захватить CS, обнаружить, что блокировка чтения доступна, но не блокировка записи, и ждать, пока другой процесс закончит чтение (потому что, если другой процесс имеет критический раздел, он будет блокируйте другие читатели, что неправильно, и если это не так, то ваш процесс не будет блокировать, а вращать, сжигая циклы процессора.)

однако то, что вы можете сделать, это использовать блокировку спина и вернуться к мьютексу всякий раз, когда есть разногласия. Критический раздел сам по себе реализуется таким образом. Я бы взял существующую реализацию критического раздела и заменил поле PID отдельными счетчиками reader & writer.


Если вы можете настроить Vista или выше, вы должны использовать встроенный SRWLock это. Они легкие, как критические разделы, полностью пользовательский режим, когда нет никаких разногласий.

блог Джо Даффи имеет некоторые последние записи при реализации различных типов неблокирующих блокировок чтения/записи. Эти замки вращаются, поэтому они не будут уместны, если вы собираетесь делать много работы, удерживая замок. Код является C#, но должен быть простым для порт родной.

вы можете реализовать блокировку чтения/записи с помощью критических разделов и событий - вам просто нужно сохранить достаточное состояние, чтобы только сигнализировать о событии, когда это необходимо, чтобы избежать ненужного вызова режима ядра.


старый вопрос, но это то, что должно работать. Она не вращается вокруг раздора. Читатели несут ограниченные дополнительные расходы, если у них мало или нет разногласий, потому что SetEvent вызывается лениво (посмотрите историю редактирования для более тяжелой версии, которая не имеет этой оптимизации).

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

вы можете уменьшить стоимость для читателей, используя один CRITICAL_SECTION:

  • countsLock заменяется writerLock в rdlock и rdunlock

  • rwlock->waitingWriter = FALSE удаляется в wrunlock

  • тело wrlock изменяется на

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

однако это теряет справедливость, поэтому я предпочитаю вышеуказанное решение.


взгляните на книгу"параллельное программирование в Windows", который имеет множество различных справочных примеров для чтения/записи замки.


Проверьте spin_rw_mutex от Intel Нить Строительные Блоки ...

spin_rw_mutex строго в пользовательской земле и использует спин-блокировки


Это старый вопрос, но, возможно, кто-то найдет это полезным. Мы разработали высокопроизводительный, открыть-источник RWLock для Windows который автоматически использует Vista+SRWLock Майкл упомянул если доступно, или иначе возвращается к реализации пользовательского пространства.

в качестве дополнительного бонуса есть четыре разных" аромата " (хотя вы можете придерживаться основного, который также является самым быстрым), каждый из которых предоставляет больше возможностей синхронизации. Она начинается с основным RWLock() который не является реентерабельным, ограничен синхронизацией одного процесса и не обменивается блокировками чтения/записи на полноценный межпроцессный IPC RWLock с поддержкой повторного входа и деэлевацией чтения/записи.

как уже упоминалось, они динамически переключаются на блокировки чтения и записи Vista+ slim для лучшей производительности, когда это возможно, но вам не нужно беспокоиться об этом вообще, так как он вернется к полностью совместимой реализации на Windows XP и ей подобных.


Если вы уже знаете о решении, которое только использует мьютексы, вы должны иметь возможность изменять его для использования критических разделов.

мы свернули наши собственные, используя две критические секции и некоторые счетчики. Это соответствует нашим потребностям - у нас очень низкое количество писателей,писатели получают преимущество над читателями и т. д. Я не вправе публиковать наши, но могу сказать, что это возможно без мьютексов и семафоров.


вот самое маленькое решение, которое я мог бы придумать:

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

и вставить дословно:

/** 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 );
    }
};

посмотрите мою реализацию здесь:

https://github.com/coolsoftware/LockLib

VRWLock-это класс C++, реализующий логику "один писатель-несколько читателей".

Посмотрите также тестовый проект TestLock.ФСЛ.

UPD. Ниже приведен простой код для чтения и записи:

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

класс VRWLock поддерживает счетчик спинов и специфичный для потока счетчик ссылок, который позволяет освободить блокировки завершенных потоков.


Я написал следующий код, используя только критические секции.

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

выполнить ожидание, комментировать строки со сном(0).