2024年4月2日发(作者:)

1/15

深入理解CRITICAL_SECTION

摘要

临界区是一种防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关

注,因而人们未能对其深刻理解。在需要跟踪代码中的多线程处理的性能时,对 Windows

中临界区的深刻理解非常有用。本文深入研究临界区的原理,以揭示在查找死锁和确认性

能问题过程中的有用信息。它还包含一个便利的实用工具程序,可以显示所有临界区及其

当前状态。

在我们许多年的编程实践中,对于 Win32 临界区没有受到非常多的“under the hood”关

注而感到非常奇怪。当然,您可能了解有关临界区初始化与使用的基础知识,但您是否曾

经花费时间来深入研究 WINNT.H 中所定义的 CRITICAL_SECTION 结构呢?在这一结构中有

一些非常有意义的好东西被长期忽略。我们将对此进行补充,并向您介绍一些很有意义的

技巧,这些技巧对于跟踪那些难以察觉的多线程处理错误非常有用。更重要的是,使用我

们的 MyCriticalSections 实用工具,可以明白如何对 CRITICAL_SECTION 进行微小地扩

展,以提供非常有用的特性,这些特性可用于调试和性能调整(要下载完整代码,参见本

文顶部的链接)。

老实说,作者们经常忽略 CRITICAL_SECTION 结构的部分原因在于它在以下两个主要

Win32 代码库中的实现有很大不同:Microsoft Windows 95 和 Windows NTH嗣侵勒饬街

执肟舛家丫⒄钩龃罅亢笮姹荆ㄆ渥钚掳姹痉直鹞 Windows Me 和 Windows XP),但没有必

要在此处将其一一列出。关键在于 Windows XP 现在已经发展得非常完善,开发商可能很

快就会停止对 Windows 95 系列操作系统的支持。我们在本文中就是这么做的。

诚然,当今最受关注的是 Microsoft .NET Framework,但是良好的旧式 Win32 编程不会

很快消失。如果您拥有采用了临界区的现有 Win32 代码,您会发现我们的工具以及对临界

区的说明都非常有用。但是请注意,我们只讨论 Windows NT 及其后续版本,而没有涉及

与 .NET 相关的任何内容,这一点非常重要。

临界区:简述

1/15

2/15

如果您非常熟悉临界区,并可以不假思索地进行应用,那就可以略过本节。否则,请向下

阅读,以对这些内容进行快速回顾。如果您不熟悉这些基础内容,则本节之后的内容就没

有太大意义。

临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段。通常在修

改全局数据(如集合类)时会使用临界区。事件、多用户终端执行程序和信号量也用于多

线程同步,但临界区与它们不同,它并不总是执行向内核模式的控制转换,这一转换成本

昂贵。稍后将会看到,要获得一个未占用临界区,事实上只需要对内存做出很少的修改,

其速度非常快。只有在尝试获得已占用临界区时,它才会跳至内核模式。这一轻量级特性

的缺点在于临界区只能用于对同一进程内的线程进行同步。

临界区由 WINNT.H 中所定义的 RTL_CRITICAL_SECTION 结构表示。因为您的 C++ 代码通

常声明一个 CRITICAL_SECTION 类型的变量,所以您可能对此并不了解。研究 WINBASE.H

后您会发现:

typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;

我们将在短时间内揭示 RTL_CRITICAL_SECTION 结构的实质。此时,重要问题在于

CRITICAL_SECTION(也称作 RTL_CRITICAL_SECTION)只是一个拥有易访问字段的结构,这

些字段可以由 KERNEL32 API 操作。

在将临界区传递给 InitializeCriticalSection 时(或者更准确地说,是在传递其地址

时),临界区即开始存在。初始化之后,代码即将临界区传递给 EnterCriticalSection

和 LeaveCriticalSection API。一个线程自 EnterCriticalSection 中返回后,所有其他

调用 EnterCriticalSection 的线程都将被阻止,直到第一个线程调用

LeaveCriticalSection 为止。最后,当不再需要该临界区时,一种良好的编码习惯是将其

传递给 DeleteCriticalSection。

在临界区未被使用的理想情况中,对 EnterCriticalSection 的调用非常快速,因为它只

是读取和修改用户模式内存中的内存位置。否则(在后文将会遇到一种例外情况),阻止

于临界区的线程有效地完成这一工作,而不需要消耗额外的 CPU 周期。所阻止的线程以内

核模式等待,在该临界区的所有者将其释放之前,不能对这些线程进行调度。如果有多个

线程被阻止于一个临界区中,当另一线程释放该临界区时,只有一个线程获得该临界区。

2/15