我在使用关键部分时遇到问题。我的应用程序有大量线程,比如 60 个,它们都需要访问全局资源。因此,我使用关键部分保护该资源。这在操作过程中非常有效,但是当我的应用程序关闭时,我触发线程退出,然后销毁临界区。
如果其中一些线程在退出时在临界区等待,从而被阻止退出自身,则会出现问题。
我已经为具有“已初始化”标志的 windows CriticalSection 调用编写了一个包装器,我在创建 crit 时将其设置为 true,并在我即将离开 crit 时设置为 false(两种情况都在内部设置暴击)。在“进入暴击”包装函数尝试进入暴击之前检查此标志,如果标志为假则绕过请求。当任何线程成功进入暴击时,也会检查该标志,如果它为假,则立即离开暴击。
在删除暴击之前我要做的是将标志设置为 false,然后等待任何等待线程: 允许进入暴击;看到 Initialised 标志为假;然后离开暴击(这对每个线程来说应该是一个非常快速的操作)。
我通过检查 CRITICAL_SECTION 结构中的 LockCount 来检查等待访问 crit 的线程数,并等到它达到 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 初始化函数:
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);
}
}