Портабельный RWLock для Windows

от автора

RWLock — это такой примитив синхронизации, позволяющий одновременное чтение и эксклюзивную запись. Т.е. чтение блокирует запись, но не блокирует чтение других тредов, а запись блокирует все.

Так вот, этот весьма полезный примитив имеется в posix threads и в Windows от Vista и далее. Для Windows XP/2003 приходится изготавливать сей, весьма полезный, примитив из двух критических секций и события.

Покажем как это выглядит (код любезно предоставлен StackOverflow и слегка переведен с C на C++):

class RWLockXP // Implementation for Windows XP { public:     RWLockXP()         : countsLock(),           writerLock(),           noReaders(),           readerCount(0),           waitingWriter(FALSE)     {         InitializeCriticalSection(&writerLock);         InitializeCriticalSection(&countsLock);         /*         * Could use a semaphore as well.  There can only be one waiter ever,         * so I'm showing an auto-reset event here.         */         noReaders = CreateEvent (NULL, FALSE, FALSE, NULL);     }      ~RWLockXP()     {         writerLock.destroy();         readerCountLock.destroy();         noReaders.destroy();     }      void readLock()     {         /**          * 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(&writerLock);         EnterCriticalSection(&countsLock);         ++readerCount;         LeaveCriticalSection(&countsLock);         LeaveCriticalSection(&writerLock);     }     void readUnLock()     {         EnterCriticalSection(&countsLock);         assert (readerCount > 0);         if (--readerCount == 0)         {             if (waitingWriter)             {                 /*                  * Clear waitingWriter here to avoid taking countsLock                  * again in wrlock.                  */                 waitingWriter = FALSE;                 SetEvent(noReaders);             }         }         LeaveCriticalSection(&countsLock);     }      void writeLock()     {         EnterCriticalSection(&writerLock);         /*          * readerCount cannot become non-zero within the writerLock CS,          * but it can become zero...          */         if (readerCount > 0)         {             EnterCriticalSection(&countsLock);              /* ... so test it again.  */             if (readerCount > 0)             {                 waitingWriter = TRUE;                 LeaveCriticalSection(&countsLock);                 WaitForSingleObject(noReaders, INFINITE);             }             else             {                 /* How lucky, no need to wait.  */                 LeaveCriticalSection(&countsLock);             }         }         /* writerLock remains locked.  */     }      void writeUnLock()     {         LeaveCriticalSection(&writerLock);     }  private:     CRITICAL_SECTION countsLock;     CRITICAL_SECTION writerLock;     HANDLE noReaders;     int readerCount;     BOOL waitingWriter; }; 

А вот как эта же красота могла бы выглядеть при использовании только Vista+ систем:

class RWLockSRW // For Windows Vista+ based on Slim RWLock { public:     RWLockSRW()         : srwLock()     {         InitializeSRWLock(&srwLock);     }      ~RWLockSRW()     {     }      void readLock()     {         AcquireSRWLockShared(&srwLock);     }      void readUnLock()     {         ReleaseSRWLockShared(&srwLock);     }      void writeLock()     {         AcquireSRWLockExclusive(&srwLock);     }      void writeUnLock()     {         ReleaseSRWLockExclusive(&srwLock);     }  private:     RTL_SRWLOCK srwLock; }; 

Мало того, что выглядит до безобразия просто, еще и работает на порядок быстрее. Но, есть одно но, как всегда… При попытке запустить приложение содержащее этот код (конечно мы умные ребята, сделали определение версии и для XP хотим использовать первый вариант, а для новых систем — второй), получим сообщение типа: «ой, а вот функции InitializeSRWLock что-то не нашлось в kernel32» после чего наше приложение любезно будет прибито.

Выход — грузить функции Slim RWLock динамически при помощи LoadLibrary, указателей на функции и этого всего:

typedef void(__stdcall *SRWLock_fptr)(PSRWLOCK);  class RWLockSRW // For Windows Vista+ based on Slim RWLock { public:     RWLockSRW()         : hGetProcIDDLL(NULL),          AcquireSRWLockShared_func(NULL),         ReleaseSRWLockShared_func(NULL),         AcquireSRWLockExclusive_func(NULL),         ReleaseSRWLockExclusive_func(NULL),         srwLock()     {         wchar_t path[MAX_PATH] = { 0 };         GetSystemDirectory(path, sizeof(path));         std::wstring dllPath = std::wstring(path) + L"\\kernel32.dll";         HINSTANCE hGetProcIDDLL = LoadLibrary(dllPath.c_str());         if (!hGetProcIDDLL)         {             throw std::exception("SRWLock Error loading kernel32.dll");         }          AcquireSRWLockShared_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "AcquireSRWLockShared");         if (!AcquireSRWLockShared_func)         {             throw std::exception("SRWLock Error loading AcquireSRWLockShared");         }          ReleaseSRWLockShared_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "ReleaseSRWLockShared");         if (!ReleaseSRWLockShared_func)         {             throw std::exception("SRWLock Error loading ReleaseSRWLockShared");         }          AcquireSRWLockExclusive_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "AcquireSRWLockExclusive");         if (!AcquireSRWLockExclusive_func)         {             throw std::exception("SRWLock Error loading AcquireSRWLockExclusive");         }          ReleaseSRWLockExclusive_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "ReleaseSRWLockExclusive");         if (!ReleaseSRWLockExclusive_func)         {             throw std::exception("SRWLock Error loading ReleaseSRWLockExclusive");         }          SRWLock_fptr InitializeSRWLock_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "InitializeSRWLock");         if (!InitializeSRWLock_func)         {             throw std::exception("SRWLock Error loading InitializeSRWLock");         }          InitializeSRWLock_func(&srwLock);     }      ~RWLockSRW()     {         if (hGetProcIDDLL)         {             FreeLibrary(hGetProcIDDLL);         }     }      void readLock()     {         if (AcquireSRWLockShared_func)         {             AcquireSRWLockShared_func(&srwLock);         }     }      void readUnLock()     {         if (ReleaseSRWLockShared_func)         {             ReleaseSRWLockShared_func(&srwLock);         }     }      void writeLock()     {         if (AcquireSRWLockExclusive_func)         {             AcquireSRWLockExclusive_func(&srwLock);         }     }      void writeUnLock()     {         if (ReleaseSRWLockExclusive_func)         {             ReleaseSRWLockExclusive_func(&srwLock);         }     }  private:     HINSTANCE hGetProcIDDLL;      SRWLock_fptr AcquireSRWLockShared_func;     SRWLock_fptr ReleaseSRWLockShared_func;     SRWLock_fptr AcquireSRWLockExclusive_func;     SRWLock_fptr ReleaseSRWLockExclusive_func;      RTL_SRWLOCK srwLock; }; 

Выглядит кучерявей, зато стало портабельно. Осталось сделать враппер автоматически выбирающий нужный вариант в зависимости от версии Windows:

class RWLock // Wrapper { public:      RWLock()          : rwLockXP(NULL), rwLockSRW(NULL), isVistaPlus(IsWindowsVistaOrGreater())      {           if (isVistaPlus)           {                rwLockSRW = new RWLockSRW();           }           else           {                rwLockXP = new RWLockXP();           }      }       ~RWLock()      {           if (isVistaPlus)           {                delete rwLockSRW;           }           else           {                delete rwLockXP;           }      }      void readLock()      {           if (isVistaPlus)           {                rwLockSRW->readLock();           }           else           {                rwLockXP->readLock();           }      }      void readUnLock()      {           if (isVistaPlus)           {                rwLockSRW->readUnLock();           }           else           {                rwLockXP->readUnLock();           }      }       void writeLock()      {           if (isVistaPlus)           {                rwLockSRW->writeLock();           }           else           {                rwLockXP->writeLock();           }      }      void writeUnLock()      {           if (isVistaPlus)           {                rwLockSRW->writeUnLock();           }           else           {                rwLockXP->writeUnLock();           }      }  private:      RWLockXP  *rwLockXP;      RWLockSRW *rwLockSRW;      bool isVistaPlus; }; 

И в завершении автолокер:

class ScopedRWLock { public:     ScopedRWLock(RWLock *lc_, bool write_ = false)         : lc(*lc_), write(write_)     {         if (write)         {             lc.writeLock();         }         else         {             lc.readLock();         }     }      ~ScopedRWLock()     {         if (write)         {             lc.writeUnLock();         }         else         {             lc.readUnLock();         }     } private:     RWLock &lc;     bool write;      // Non copyable!     static void *operator new(size_t);     static void operator delete(void *);     ScopedRWLock(const ScopedRWLock&);     void operator=(const ScopedRWLock&); }; 

Реализация с использованием pthread ничем не отличается от первой версии SRWLock за исключением других имен вызываемых функций.
ссылка на оригинал статьи https://habrahabr.ru/post/317958/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *