У меня проблема с использованием критических секций. В моем приложении большое количество потоков, скажем, 60, и всем им нужен доступ к глобальному ресурсу. Поэтому я защищаю этот ресурс критическим разделом. Это отлично работает во время работы, однако, когда мое приложение закрывается, я запускаю потоки для выхода, а затем уничтожаю критическую секцию.
Проблема возникает, если некоторые из этих потоков ожидают в критической секции во время выхода и, таким образом, не могут завершиться самостоятельно.
Я написал оболочку для вызовов Windows CriticalSection с флагом «Инициализировано», который я устанавливаю в значение «истина» при создании крита и устанавливаю в «ложь» когда собираюсь выйти из крита (оба случая устанавливается внутри крита). Этот флаг проверяется до того, как функция-оболочка 'enter crit' попытается ввести крит, минуя запрос, если флаг ложный. Флаг также проверяется в тот момент, когда любой поток успешно входит в крит, заставляя его немедленно покинуть крит, если он ложный.
Прежде чем удалить крит, я должен установить флаг в false, а затем дождаться, пока все ожидающие потоки: будут допущены к крит; см., что флаг Initialized имеет значение false; затем оставьте крит (который должен быть довольно быстрым для каждого потока).
Я проверяю количество потоков, ожидающих доступа к крит, проверяя LockCount внутри структуры CRITICAL_SECTION и ожидая, пока он не достигнет 0 (в XP это LockCount - (RecursionCount-1)
; в 2003 и выше, количество блокировок ((-1) - (LockCount)) >> 2
), прежде чем я уничтожу критическую секцию.
Этого должнобыть достаточно, однако я обнаружил, что LockCount достигает 0, когда есть еще один поток (всегда только один поток, никогда больше), ожидающий входа в крит, то есть, если я удалю крит в этот момент другой поток впоследствии просыпается от ожидания критического значения и вызывает сбой, поскольку объект CRITICAL_SECTION к тому времени был уничтожен.
Если я сохраняю свой собственный внутренний счетчик блокировок потоков, ожидающих доступа, у меня есть правильный счет; однако это не идеально, так как я должен увеличивать это значение за пределами критического значения, а это означает, что это значение не защищено и, следовательно, на него нельзя полностью полагаться в любой момент времени.
Кто-нибудь знает, почему значение LockCount в структуре CRITICAL_SECTION меньше 1? Если я использую свой собственный счетчик блокировок, то проверяю счетчик блокировок CRITICAL_SECTION после выхода последнего потока (и до того, как я уничтожу крит), он по-прежнему равен 0...
Или есть лучший как мне защитить глобальный ресурс в моем приложении с таким количеством потоков, кроме критического раздела?
Это моя структура-оболочка:
typedef struct MY_CRIT {
BOOL Initialised;
CRITICAL_SECTION Crit;
int MyLockCount;
}
Вот моя функция инициализации Crit:
BOOL InitCrit( MY_CRIT *pCrit )
{
if (pCrit)
{
InitializeCriticalSection( &pCrit->Crit );
pCrit->Initialised = TRUE;
pCrit->MyLockCount = 0;
return TRUE;
}
// else invalid pointer
else
return FALSE;
}
Это моя функция-оболочка ввода критического значения:
BOOL EnterCrit( MY_CRIT *pCrit )
{
// if pointer valid, and the crit is initialised
if (pCrit && pCrit->Initialised)
{
pCrit->MyLockCount++;
EnterCriticalSection( &pCrit->Crit );
pCrit->MyLockCount--;
// if still initialised
if (pCrit->Initialised)
{
return TRUE;
}
// else someone's trying to close this crit - jump out now!
else
{
LeaveCriticalSection( &pCrit->Crit );
return FALSE;
}
}
else // crit pointer is null
return FALSE;
}
А вот моя функция-оболочка FreeCrit:
void FreeCrit( MY_CRIT *pCrit )
{
LONG WaitingCount = 0;
if (pCrit && (pCrit->Initialised))
{
// set Initialised to FALSE to stop any more threads trying to get in from now on:
EnterCriticalSection( &pCrit->Crit );
pCrit->Initialised = FALSE;
LeaveCriticalSection( &pCrit->Crit );
// loop until all waiting threads have gained access and finished:
do {
EnterCriticalSection( &pCrit->Crit );
// check if any threads are still waiting to enter:
// Windows XP and below:
if (IsWindowsXPOrBelow())
{
if ((pCrit->Crit.LockCount > 0) && ((pCrit->Crit.RecursionCount - 1) >= 0))
WaitingCount = pCrit->Crit.LockCount - (pCrit->Crit.RecursionCount - 1);
else
WaitingCount = 0;
}
// Windows 2003 Server and above:
else
{
WaitingCount = ((-1) - (pCrit->Crit.LockCount)) >> 2;
}
// hack: if our own lock count is higher, use that:
WaitingCount = max( WaitingCount, pCrit->MyLockCount );
// if some threads are still waiting, leave the crit and sleep a bit, to give them a chance to enter & exit:
if (WaitingCount > 0)
{
LeaveCriticalSection( &pCrit->Crit );
// don't hog the processor:
Sleep( 1 );
}
// when no other threads are waiting to enter, we can safely delete the crit (and leave the loop):
else
{
DeleteCriticalSection( &pCrit->Crit );
}
} while (WaitingCount > 0);
}
}