문제

직장 프로젝트의 일환으로 Win32 API를 사용하여 C ++에서 읽기/쓰기 잠금을 구현해야합니다. 기존의 모든 솔루션은 실행 중에 컨텍스트 스위치가 필요한 커널 객체 (세마포어 및 뮤 테스)를 사용합니다. 내 응용 프로그램에는 너무 느립니다.

가능하면 중요한 섹션 만 사용하여 하나를 구현하고 싶습니다. 잠금 장치는 프로세스 안전 할 필요가 없으며 ThreadSafe 만 있습니다. 이것에 대한 방법에 대한 아이디어가 있습니까?

도움이 되었습니까?

해결책

잠금 장치를 사용할 수있을 때까지 호출 프로세스 블록을 만들기 위해 커널의 도움이 필요하기 때문에 적어도 하나의 커널 레벨 객체 (MuTex 또는 Semaphore)를 사용하지 않고는이 작업을 수행 할 수 없다고 생각합니다.

중요한 섹션은 차단을 제공하지만 API는 너무 제한적입니다. 예를 들어 CS를 잡을 수없고 읽기 잠금 장치를 사용할 수 있지만 쓰기 잠금 장치가 없음을 발견하고 다른 프로세스가 읽기를 완료 할 때까지 기다립니다 (다른 프로세스에 중요한 섹션이 있으면 다른 독자가 잘못된 독자를 차단하고 그러면 프로세스가 차단되지 않고 회전하여 CPU 사이클을 태우지 않습니다.)

그러나 당신이 할 수있는 일은 스핀 잠금 장치를 사용하고 경합이있을 때마다 뮤텍스로 돌아가는 것입니다. 중요한 섹션 자체는 이런 식으로 구현됩니다. 기존의 중요한 섹션 구현을 취하고 PID 필드를 별도의 독자 및 작가 수로 바꾸겠습니다.

다른 팁

Vista 이상을 타겟팅 할 수 있다면 내장을 사용해야합니다. srwlock 's. 그것들은 중요한 섹션과 같은 가벼운 부분이며, 경합이 없을 때 완전히 사용자 모드입니다.

Joe Duffy의 블로그에는 최근 몇 가지가 있습니다 항목 다른 유형의 비 블로킹 리더/작가 잠금 장치 구현. 이 자물쇠는 회전하므로 자물쇠를 잡고있는 동안 많은 작업을하려고한다면 적절하지 않습니다. 코드는 C#이지만 포트로 간단해야합니다.

중요한 섹션 및 이벤트를 사용하여 Reader/Writer Lock을 구현할 수 있습니다. 불필요한 커널 모드 호출을 피하기 위해 필요한 경우에만 이벤트에 대해서만 알리기에 충분한 상태를 유지하면됩니다.

오래된 질문이지만 이것은 효과가있는 것입니다. 경합에 회전하지 않습니다. 독자는 경합이 거의 없거나 전혀없는 경우 추가 비용이 제한됩니다. SetEvent Lazily라고 불립니다 (이 최적화가없는보다 헤비급 버전의 편집 기록을보십시오).

#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 인텔에서 스레드 빌딩 블록 ...

spin_rw_mutex 엄격하게 사용자 랜드에 있으며 차단을 위해 스핀 웨이트를 사용합니다

이것은 오래된 질문이지만 아마도 누군가가 이것이 유용하다고 생각할 것입니다. 우리는 고성능을 개발했습니다. 오픈 소스 RWLock Windows 용 Vista+를 자동으로 사용합니다 SRWLock 마이클은 언급했다 사용 가능한 경우 또는 그렇지 않으면 사용자 공간 구현으로 돌아갑니다.

추가 보너스로서, 네 가지 다른 "맛"이 있습니다 (기본을 고수 할 수 있지만 가장 빠른 기본을 고수 할 수 있지만). 기본으로 시작합니다 RWLock() 이는 반복적이지 않으며 단일 프로세스 동기화로 제한되며, 읽기/쓰기 잠금 장치를 본격적인 크로스 프로세스 IPC RWLOCK으로 재입고 지원 및 읽기/쓰기로 교체하지 않습니다.

언급 한 바와 같이, 그들은 가능한 경우 최상의 성능을 위해 Vista+ Slim Read-Write 잠금 장치로 동적으로 교환하지만 Windows XP와 그에서 완전히 호환 가능한 구현으로 돌아갈 것이므로 전혀 걱정할 필요가 없습니다. ILK.

당신이 이미 해결책을 알고 있다면 MUTEXE를 사용하려면 대신 중요한 섹션을 사용하도록 수정할 수 있어야합니다.

우리는 두 개의 중요한 섹션과 일부 카운터를 사용하여 직접 굴 렸습니다. 그것은 우리의 요구에 맞습니다 - 우리는 작가 수가 매우 낮고, 작가는 독자보다 우선합니다. 나는 우리의 출판에 자유롭지는 않지만 뮤 테스와 세마포어 없이는 가능하다고 말할 수 있습니다.

다음은 다음과 같이 생각해 낼 수있는 가장 작은 솔루션입니다.

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

그리고 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 );
    }
};

여기에서 내 구현을보십시오.

https://github.com/coolsoftware/locklib

vrwlock은 단일 작가 - 다중 독자 논리를 구현하는 C ++ 클래스입니다.

테스트 프로젝트 testlock.sln도보십시오.

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).

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top