2009-06-17 70 views
8

我要實現在C++中使用Win32 API作爲工作項目的一部分讀/寫鎖。所有現有的解決方案都使用內核對象(信號量和互斥量),這些對象在執行過程中需要上下文切換。這對我的應用來說太慢了。的Win32讀/寫鎖僅使用臨界區

我想實現一個只使用關鍵部分,如果可能的話。鎖不必是安全的,只有線程安全。有關如何去做這件事的任何想法?

回答

6

我不認爲這可以在不使用至少一個內核級的對象(互斥或信號)來完成的,因爲你需要的內核的幫助,使調用進程阻塞,直到鎖可用。

關鍵部分確實提供了阻塞,但API太有限。例如你不能抓住一個CS,發現有一個讀鎖可用,但不是寫鎖,並等待另一個進程完成讀取(因爲如果另一個進程有關鍵部分,它會阻止其他讀者出錯,如果它不那麼你的程序將不會阻止,但旋轉,燃燒的CPU週期。)

但是你可以做的是使用一個自旋鎖,並回落到一個互斥每當有爭。關鍵部分本身就是這樣實施的。我將採用現有的關鍵部分實現,並用單獨的讀取器&寫入器計數替換PID字段。

0

如果您已經知道只有使用互斥體的解決方案,那麼您應該可以將其修改爲使用臨界區域。

我們使用兩個關鍵部分和一些計數器來滾動我們自己。它適合我們的需求 - 我們的寫作者數量非常低,作者優先於讀者等。我無權發佈我們的內容,但可以說沒有互斥和信號量是可能的。

+1

問題是,使用標準互斥鎖,任何人都可以鎖定/解鎖它。對於關鍵部分來說這不是真的,這使我的解決方案無法正常工作。 – 2009-06-17 18:38:00

+0

不清楚你的意思 - 只有擁有mutext的線程才能解鎖它。一旦互斥鎖被解鎖,任何人都可以鎖定它。同樣有關鍵部分。 – 2009-06-17 18:40:53

+1

對不起,我的意思是一個信號量。我需要一個解決方案,任何人都可以減少信號量,這對於關鍵部分是不可能的。 – 2009-06-17 18:42:30

11

如果你可以針對Vista或更高,你應該使用內置的SRWLock's。它們像關鍵部分一樣輕便,在沒有爭用時完全是用戶模式。

喬·達菲的博客對實現不同類型的非阻塞讀/寫鎖的一些最新entries。這些鎖旋轉,所以如果你打算在鎖的同時做很多工作,它們就不合適。代碼是C#,但應該直接移植到本機。

可以實現使用臨界區和事件讀取/寫入器鎖 - 你只需要保持足夠的狀態只有信號的情況下,必要時,以避免不必要的內核模式調用。

0

這裏是我能想出最小的解決方案:

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

老問題,但是這是應該工作。它不會爭用。如果讀者很少或沒有爭用,讀者會承擔額外的有限費用,因爲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被替換爲rdlock和rdunlock writerLock

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

然而,這在失去公平,所以我更喜歡上面的解決方案。

1

這是一個老問題,但也許有人會覺得這很有用。我們開發了一個高性能的open-source RWLock for Windows,它自動使用Vista + SRWLockMichael mentioned(如果可用)或以其他方式回退到用戶空間實施。

作爲一個額外的好處,它有四種不同的「風味」(儘管你可以堅持基本的,也是最快的),每個提供更多的同步選項。它從不可重入的基本RWLock()開始,僅限於單進程同步,並且不會將讀/寫鎖交換到具有重入入支持和讀/寫降低功能的完整跨進程IPC RWLock。

如前所述,它們會動態替換爲Vista +超薄讀寫鎖,以儘可能獲得最佳性能,但您根本無需擔心,因爲它會回退到完全兼容的實現Windows XP及其同類。

0

看我在這裏實現:

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類支持旋轉數和線程特定的引用計數,允許釋放終止線程鎖。

0

我只寫了下面的代碼,只使用了關鍵部分。

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

要執行旋轉等待,請將行註釋爲Sleep(0)。