从内部的视角看,一个临界区是一套计数器和标志位的集合,也可能是一个事件对象。
(请注意,临界区的内部结构随时可能更改,事实上,它在 Windows XP 和 Windows 2003 之间发生了变化。因此,此处提供的信息仅用于故障排除和调试目的,不用于生产用途。)
只要没有争用,计数器和标志位就足够了,因为没有人必须等待临界区(因此,当临界部分区时,没有人必须被唤醒)。
如果一个线程需要被阻塞,因为它锁定的临界区已经被另一个线程拥有,内核会为临界区创建一个事件(如果还没有),并等待它。
当临界区的所有者最终释放它时,会激发该事件的信号,从而提醒所有等待的线程:”关键部分现在可用”,他们应该尝试再次锁定它。(如果有多个等待线程,则只有一个线程将实际进入临界区,其他人将继续等待。)
如果在 LeaveCriticalSection 中收到无效的句柄异常,则表示临界区的代码认为有其他线程在等待临界区可用,因此它尝试发出事件信号,但事件句柄处于无效状态。
现在,请先思考一下,为什么会这样。
一种可能性是,临界区内存已被破坏,并且通常保存事件句柄的内存已被其他一些恰好不是有效句柄值覆盖。
另一种可能性是,其他一些代码段将未初始化的变量传递给 CloseHandle 函数,并最终错误地关闭了临界区的句柄。如果其他代码段存在双重关闭错误,并且句柄(现已关闭)恰好被重用作临界区的事件句柄,也会发生这种情况。当有缺陷的代码第二次错误地关闭句柄时,它最终会关闭临界区的句柄。
当然,问题可能是临界区无效,因为它从一开始就没有被正确地初始化过。字段中的值只是未初始化的垃圾,当你尝试离开此未初始化的临界区时,该垃圾将用作事件句柄,从而引发无效句柄异常。
话又说回来,问题可能是临界部分无效,因为它已经被销毁了。例如,一个线程可能具有如下代码:
EnterCriticalSection(&cs);
… do stuff…
LeaveCriticalSection(&cs);
当该线程忙于执行操作时,另一个线程会调用 DeleteCriticalSection(&cs)。这会破坏临界区,而另一个线程仍在使用它。最终,该线程完成其操作并调用 LeaveCriticalSection,这会引发无效句柄异常,因为 DeleteCriticalSection 已关闭句柄。
所有这些都是 LeaveCriticalSection 中出现无效句柄异常的可能原因。要确定你遇到的是哪一个,需要更多的调试,但至少现在你知道要寻找什么。
总结
临界区必须成对进行操作,且需要考虑某些异常导致的代码提前跳出正常执行路径,而造成的不能成对调用的情况。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《What does an invalid handle exception in LeaveCriticalSection mean?》