Make sure the threads do not wait in the critical section before destroying it

I have a problem using critical sections. My application has a large number of threads, say 60, which all need access to a global resource. Therefore, I protect this resource with a critical sector. This works great while running, however, when my application shuts down, I start the threads to exit and then destroy the critical section.

A problem arises if some of these threads are waiting in the critical section during exit and, thus, are blocked upon exit from it.

I wrote a wrapper around the Windows CriticalSection calls with the “Initialised” flag, which I set to true when creating the crit and set it to false when I was going to leave crit (both cases: set when inside the crit). This flag is checked before the "enter crit" function tries to enter crit, bypassing the request if the flag is false. The flag is also checked at the moment when any thread successfully enters crit, so it immediately leaves crit if it is erroneous.

What should I do before removing the crit, set the flag to false, and then wait until all pending threads are: allowed in crit; see flag Initialized is false; then leave crit (which should be pretty fast for each thread).

I check the number of threads waiting to access the critical criterion, checking the LockCount inside the CRITICAL_SECTION structure and waiting until it reaches 0 (in XP,; LockCount - (RecursionCount-1)in 2003 the server and higher the number of locks ((-1) - (LockCount)) >> 2), before I then destroy the critical section.

This should be enough, however, I found that LockCount reaches 0 when there is another thread (always only one thread, never again) waiting to enter crit, that is, if I remove crit at this moment, another thread then wakes up from waiting on crit and causes a crash, since the CRITICAL_SECTION object had been destroyed by then.

, , ; , , , , , .

- , LockCount CRITICAL_SECTION 1? , CRITICAL_SECTION , ( , ), 0...

, , ?

struct:

typedef struct MY_CRIT {
    BOOL Initialised;
    CRITICAL_SECTION Crit;
    int MyLockCount;
}

init init:

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 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);
    }
}
+3
1

, CS , . , , , . CS, ? , ?

, , , , , , , . , , .

, CS, .LockCount, , , , IsWindowsXPOrBelow. API , CRITICAL_SECTION " ", .

+4

All Articles