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

У меня проблема с использованием критических секций. В моем приложении большое количество потоков, скажем, 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);
    }
}
5
задан Nick Shaw 14 May 2012 в 11:49
поделиться