确保在销毁之前没有线程在关键部分上等待

时间:2012-05-14 11:32:12

标签: c winapi critical-section

我在使用关键部分时遇到了问题。我的应用程序有大量的线程,比如60,它们都需要访问全局资源。因此,我通过关键部分保护该资源。这在操作期间完美地工作,但是当我的应用程序关闭时,我触发线程退出,然后销毁关键部分。

如果其中一些线程在退出时等待临界区,则会出现问题,因此无法自行退出。

我在Windows CriticalSection调用周围写了一个包装器,它有一个'Initialised'标志,我在创建暴击时设置为true,当我即将离开暴击时设置为false(两种情况都是在暴击里面设置)。在'enter crit'包装函数尝试进入暴击之前检查此标志,如果标志为假则绕过请求。当任何线程成功进入暴击时,也会检查该标志,如果它是假的,立即离开暴击。

在删除暴击之前我要做的是将旗帜设置为false,然后等待任何等待的线程:被允许进入暴击;看Initialised标志是false;然后离开暴击(这应该是每个线程非常快速的操作)。

我通过检查CRITICAL_SECTION结构中的LockCount来检查等待访问暴击的线程数,并等到它达到0(在XP中,那是LockCount - (RecursionCount-1);在2003服务器及以上,锁定计数在我破坏关键部分之前是((-1) - (LockCount)) >> 2)。

这个应该就足够了,但是当我还有一个线程(总是只有一个线程,永远不会更多)等待输入暴击时,我发现LockCount达到0,这意味着我删除了此时,另一个线程随后从等待暴击中醒来,并导致崩溃,因为此时CRITICAL_SECTION对象已被破坏。

如果我保持自己内部锁定的线程数等待访问,我有正确的计数;然而这并不理想,因为我必须在暴击之外增加这个数,这意味着价值不受保护,因此在任何时候都不能完全依赖。

有谁知道为什么CRITICAL_SECTION结构中的LockCount会被1除外?如果我使用自己的锁定计数,那么在最后一个线程退出之后(以及在我销毁暴击之前)检查CRITICAL_SECTION的锁定计数,它仍然是0 ...

或者,除了关键部分之外,还有更好的方法来保护我的应用中的全局资源吗?

这是我的包装结构:

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

这是我的Crit 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'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);
    }
}

1 个答案:

答案 0 :(得分:5)

您有责任确保CS在销毁之前不再使用。让我们说没有其他线程正在尝试进入,但它有可能很快就会尝试。现在你破坏CS,这个并发线程将要做什么?它的全部速度会导致删除的关键部分导致内存访问冲突?

实际的解决方案取决于您当前的应用程序设计,但如果您正在销毁线程,那么您可能希望标记您的请求以停止这些线程,然后等待该句柄等待它们的销毁。然后在确定线程完成时完成删除关键部分。

请注意,依赖诸如.LockCount之类的CS成员值并做正确的事情是不安全的,甚至不需要IsWindowsXPOrBelow这样的事情。关键部分API建议您使用CRITICAL_SECTION结构作为“黑匣子”,使内部结构具体实现。