我在使用关键部分时有一个问题。我的应用程序有很多线程,比如说60个,它们都需要访问全局资源。因此,我用一个关键部分来保护资源。这在操作过程中非常有效,但是当我的应用程序关闭时,我触发线程退出,然后销毁关键部分。
问题是,如果其中一些线程在退出时等待关键部分,从而被阻止退出。
我已经为windows criticalsection调用编写了一个包装,它有一个“initialized”标志,在crit创建时设置为true,在即将离开crit时设置为false(这两种情况都在crit内部设置)。在“enter crit”包装函数尝试输入crit之前检查此标志,如果标志为false,则绕过请求。当任何线程成功进入暴击时,标志也会被检查,如果它是假的,它会立即离开暴击。
在删除crit之前,我要做的是将标志设置为false,然后等待任何等待的线程:允许进入crit;看到初始化的标志为false;然后离开crit(对每个线程来说,这应该是一个非常快速的操作)。
我通过检查critical_section结构中的锁计数来检查等待访问crit的线程数,并等待它达到0(在xp中,这是LockCount - (RecursionCount-1);在2003服务器及更高版本中,锁计数是((-1) - (LockCount)) >> 2),然后销毁critical section。
这应该足够了,但是我发现当仍然有一个线程(总是只有一个线程,永远不会更多)等待进入crit时,锁计数达到0,这意味着如果我在那一点上删除crit,另一个线程随后从等待crit中醒来,并导致崩溃,因为此时关键截面对象已被销毁。
如果我保持自己的线程内部锁计数等待访问,那么我拥有正确的计数;但是这并不理想,因为我必须在crit之外增加该计数,这意味着该值不受保护,因此在任何时候都不能完全依赖该值。
有人知道为什么critical_section struct中的锁计数会为1吗?如果我使用我自己的锁计数,然后检查最后一个线程退出后(在我销毁CRIT之前)的临界值的锁计数,它仍然是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;
}

这是我的enter crit包装函数:
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);
    }
}

最佳答案

你有责任在销毁之前确保CS不再使用。让我们说,目前没有其他线索试图进入,但有可能它将很快尝试。现在销毁cs,这个并发线程将做什么?它会全速点击导致内存访问冲突的已删除关键部分?
实际的解决方案取决于您当前的应用程序设计,但如果您正在销毁线程,则可能需要标记您的请求以停止这些线程,然后等待ITH句柄等待其销毁。然后在确定线程已完成时删除关键部分。
注意,依赖于cs成员值(如.LockCount)是不安全的,并且正确地执行了操作之后,您甚至不需要像IsWindowsXPOrBelow这样的操作。关键部分api建议您使用CRITICAL_SECTION结构作为“黑盒”,让内部组件特定于实现。

09-06 22:32