четверг, 19 декабря 2013 г.

LockLib

LockLib это набор классов для организации доступа к разделяемым ресурсам в программе на C++ под Windows.
Исходники доступны на GitHub: https://github.com/coolsoftware/LockLib.

class VLock


Класс VLock используется как альтернатива CRITICAL_SECTION (на самом деле это "обертка" над CRITICAL_SECTION).

void Lock(int lPosition, volatile LONG * lpThreadLock = NULL)


Блокировка ресурса для монопольного использования. Если ресурс уже кем-то заблокирован, то происходит ожидание когда ресурс снова станет свободен и его удастся заблокировать.

Параметр lPosition служит для идентификации места вызова метода Lock и может использоваться при отладке.

Необязательный параметр lpThreadLock служит для подсчета вызовов метода Lock в текущем потоке. Подробности смотрите ниже в разделе посвященном lpThreadLock.

void Unlock(volatile LONG * lpThreadLock = NULL)


Снятие блокировки.

static void OutputDebugLocks()


При отладке и оптимизации приложений иногда нужно видеть список всех существующих блокировок и статистику по ним: сколько в данный момент активных блокировок, в каком месте они заблокированы. Посмотреть такую статистику можно вызвав OutputDebugLocks. Эта статистика доступна в Debug-версии приложении, когда объявлен _DEBUG, или когда объявлен макрос DEBUG_LOCK.

volatile LONG * lpThreadLock


Проблема с блокировками в много-поточном приложении связана с тем, что поток, который установил блокировку, может быть остановлен с помощью TerminateThread. В этом случае Unlock не будет вызван никогда и блокировка ресурса "повиснет", а другие потоки, которые попытаются заблокировать этот ресурс, также "повиснут".
Чтобы разрулить эту ситуацию, необходимо вести подсчет блокировок для каждого потока и вызывать Unlock после TerminateThread.

  1. VLock lock;
  2.  
  3. unsigned int __stdcall LockThreadProc(void * lpParam)
  4. {
  5.     lock.Lock(1, reinterpret_cast<volatile LONG *>(lpParam)); //lock resource
  6.  
  7.     //do something here
  8.  
  9.     lock.Unlock( reinterpret_cast<volatile LONG *>(lpParam)); //unlock resource
  10.  
  11.     //contuinue working
  12.  
  13.     return 0;
  14. }
  15.  
  16. void main(int argc, char* argv[])
  17. {
  18.     volatile LONG lThreadLock = 0; //initialize with zero
  19.     //create thread
  20.     HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, LockThreadProc, (void*)&lThreadLock, CREATE_SUSPENDED, NULL);
  21.     //start thread
  22.     ::ResumeThread(hThread);
  23.     //wait for 5 seconds
  24.     if(::WaitForSingleObject(hThread, 5000) == WAIT_TIMEOUT)
  25.     {
  26.         //terminate thread
  27.         ::TerminateThread(hThread, 0);
  28.         //release lock
  29.         lock.Unlock(&lThreadLock);
  30.     }
  31.     //close thread handle
  32.     ::CloseHandle(hThread);
  33. }

class VRWLock


Класс VRWLock позволяет реализовать стратегию блокировки ресурса "один писатель - много читателей".

VRWLock(LONG lMaxReaders = 65535, DWORD dwSpinCount = 1000, DWORD dwTimeout = 5)


Конструктор класса VRWLock имеет следующие параметры:

lMaxReaders - максимальное количество читателей, которые могут одновременно читать ресурс (не должно быть = 0!). Значение по-умолчанию 65535.

dwSpinCount - количество неудачных попыток блокировки ресурса (занятого), после которых происходит переключение контекста с небольшой задержкой, определяемой параметром dwTimeout (в миллисекундах).

void LockRead(int lPosition, volatile LONG * lpThreadLock = NULL)


Блокировка ресурса читателем. Если ресурс занят писателем или превышено максимальное количество читателей, то происходит ожидание освобождения ресурса, когда удастся его заблокировать. Смотрите описание параметров в описании метода Lock класса VLock.

void LockWrite(int lPosition, volatile LONG * lpThreadLock = NULL)


Блокировка ресурса писателем. Если ресурс занят писателем или одним или несколькими читателями, то происходит ожидание освобождения ресурса, когда удастся его заблокировать. Смотрите описание параметров в описании метода Lock класса VLock.

void ReLockWrite(int lPosition, volatile LONG * lpThreadLock = NULL)


Изменение статуса блокировки с читателя на писателя. Если ресурс не занят ни читателем ни писателем, то действие функции аналогично LockWrite (блокировка писателем). Если ресурс занят одним читателем, то происходит переключение его статуса с читателя на писателя. Если ресурс занят писателем или более чем одним читателем, то происходит ожидание момента, когда ресурс будет не занят писателями и занят не более чем одним читателем. Смотрите описание параметров в описании метода Lock класса VLock.

void Unlock(volatile LONG * lpThreadLock = NULL)


Снятие блокировки.

static void OutputDebugLocks()


Вывод статистики блокировок (см. выше описание одноименного метода для класса VLock).

class VLockPtr, class VReadLockPtr, class VWriteLockPtr


Есть такая идиома: RAII - захват ресурса есть инициализация. Суть ее в следующем: создается класс-обертка такой, что в конструкторе класса вызывается соответствующая функция блокировки ресурса, а в деструкторе блокировка снимается. Это удобно по двум причинам:
  1. Нет необходимости делать явный вызов Unlock (а это часто, как показывает практика, забывают сделать).
  2. В случае возбуждения исключения между вызовами Lock и Unlock ресурс может оказаться занят "навсегда". А при использования RAII, деструктор класса-обертки, а следовательно и Unlock, будет вызван и в случае исключительной ситуации.
VLockPtr - RAII класс-обертка над VLock.
VReadLockPtr - RAII класс-обертка над VRWLock::LockRead.
VWriteLockPtr - RAII класс-обертка над VRWLock::LockWrite.

  1. unsigned int __stdcall LockPtrThreadProc(void * lpParam)
  2. {
  3.     {
  4.         VLockPtr lockptr(&lock, 1, reinterpret_cast<volatile LONG *>(lpParam)); //lock resource
  5.  
  6.         //do something here
  7.  
  8.     } //unlock will be done here
  9.  
  10.     //contuinue working
  11.  
  12.     return 0;
  13. }

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Комментариев нет:

Отправить комментарий