摘要

在 Microsoft Windows 平台上有几种以原子方式锁定代码和数据的不同方法。此白皮书的主要目的是向开发人员简要介绍 Windows 中进行锁定的不同方法以及与这些锁定有关的相应性能开销。因为未来架构将是多核架构,因此此信息非常适用。

简介
多线程软件应用对于提升英特尔内核架构的性能至关重要。锁定代码通常是多线程应用中运行最频繁的代码。确定要使用的锁定方法与确定应用中并行处理一样重要。此白皮书的主要目的是向开发人员简要介绍 Windows 中进行锁定的不同方法以及与这些锁定有关的相应性能开销。Window 的某些锁定 API 可能会跳转至操作系统内核。此白皮书将详细说明跳转至内核的 API 以及相应的加入条件。

使用两种不同的锁定内核说明表示不同粒度的锁定的影响。第一种锁定内核模拟锁定和取消锁定动态链接列表(通常用于通过内存管理器维护一个空闲列表)的场景。对于多线程应用,首先需要锁定此列表,线程才能尝试分配或释放内存。第二种锁定内核表示更加精细地锁定,因为它仅获取已获得锁定的线程的 ThreadID,更新全局变量,然后释放锁定。通过采用 1 至 64 的线程数,对高、低争用场景下不同锁定的性能进行测试。每一线程获取执行 10,000,000 次某一运算的锁定,然后释放此锁定。对于这些实验,Windows XP 操作系统计时从 10 毫秒更改为 1 毫秒。
WaitForSingleObject 和 EnterCriticalSection
Microsoft Windows 平台中两种最常用的锁定方法为 WaitForSingleObject 和 EnterCriticalSection。WaitForSingleObject 是一个过载 Microsoft API,可用于检查和修改许多不同对象(如事件、作业、互斥体、进程、信号、线程或计时器)的状态。WaitForSingleObject 的一个不足之处是它会始终获取内核的锁定,因此无论是否获得锁定,它都会进入特权模式 (环路 0)。此 API 还进入 Windows 内核,即使指定的超时为 0,亦如此。此锁定方法的另一不足之处在于,它一次只能处理 64 个尝试对某个对象进行锁定的线程。WaitForSingleObject 的优点是它可以全局进行处理,这使得此 API 能够用于进程间的同步。它还具有为操作系统提供锁定对象信息的优势,从而可以实现公平性及优先级倒置。

通过对关键代码段实施 EnterCriticalSection 和 LeaveCriticalSection API 调用,可以使用 EnterCriticalSection。此 API 具有 WaitForSingleObject 所不具备的优点,因为只有存在锁定争用时,才会进入内核。如果不存在锁定争用,则此 API 会获取用户空间锁定,并且在未进入特权模式的情况下返回。如果存在争用,则此 API 在内核中所采用的路径将与 WaitForSingleObject 极其相似。在低争用的情况下,由于 EnterCriticalSection 不进入内核,因此锁定开销非常低。

不足之处是 EnterCriticalSection 无法进行全局处理,因此无法为线程获取锁定的顺序提供任何保证。EnterCriticalSection 是一种阻塞调用,意味着只有线程获得对此关键区段的访问权限时,该调用才会返回。Windows 引入了 TryEnterCriticalSection,TryEnterCriticalSection 是一种非阻塞调用,无论获得锁定与否都会立即返回。此外,EnterCriticalSection 还允许开发人员使用自旋计数对关键区段进行初始化,在回退前线程会按此自旋计数尝试获取锁定。通过使用 API InitializeCriticalSectionAndSpinCount,完成初始化。自旋计数可以在此调用中进行设置,也可以在注册表中进行设置,以根据不同操作系统及其相应的线程量程对自旋进行更改。

如果存在锁定争用,则 EnterCriticalSection 和 WaitForSingleObject 都会进入内核。如果实现程度过高,从用户模式到特权模式的转换开销将会非常大。

EnterCriticalSection 和 WaitForSingleObject API 调用在对使用数千个周期的运算进行锁定时,通常不会影响性能。在这些情况下,锁定调用本身的开销不会如此突出。会导致性能降低的情况是粒度锁定,获得和释放此锁定要花费数百个周期。在这些情况下,使用用户级别锁定则非常有益。

为了说明在低争用的情况下 WaitForSingleObject 调用与 EnterCriticalSection 调用的开销情况,我们分别在 1 个和 2 个线程上运行了内存管理锁定内核。在低争用的情况下,存在加速比 (WaitForSingleObject_Time / EnterCriticalSection_Time) 大约为 5 倍的性能之差。在 2 个线程持续争用的情况下,使用 EnterCriticalSection 和使用 WaitForSingleObject 之间的差别最小。在低争用的情况下存在性能差距的原因如下:WaitForSingleObject 在每次调用时都进入内核,而 EnterCriticalSection 只有当存在锁定争用时,才进入内核。

线程数量    EnterCriticalSection 时间(毫秒)    WaitForSingleObject 时间(毫秒)    加速比
1个线程(无争用)   1781                                                 9187                                5.2
2 个线程(争用)    53594                                                58156                               1.1

图1

图 1:显示了在具有 1 个和 2 个线程的情况下,EnterCriticalSection 和 WaitForSingleObject 所对应的内存管理内核情况。EnterCriticalSection 在 1 个线程(无争用)的情况下速度较快,因为如果获得锁定,EnterCriticalSection 不会跳转至内核(特权模式)。

下图 2 说明了 EnterCriticalSection 和 WaitForSingleObject 在高争用时的开销情况,所采用的线程数介于 1 至 64 之间。在本实验中,我们在向动态链接列表中推入值和从中弹出值的同时,锁定和取消锁定该列表。目的是模拟内存分配器空闲列表,为了分配或释放内存,需频繁锁定该列表。内核会主动根据环境切换争用锁定的线程,因此在两次实验中,CPU 平均负载都是 22%。很明显,在高争用情况下,利用 EnterCriticalSection 和 WaitForSingleObject 的开销并无太大差别。

image

图 2:显示了在具有 1 到 64 条线程的情况下,EnterCriticalSection 和 WaitForSingleObject 所对应的内存管理内核情况。在高争用情况下,与 WaitForSingleObject 和 EnterCriticalSection 所关联的开销并无太大差别。

利用英特尔® VTune 分析器通过基于事件的采样来收集时钟滴答事件,这对于确定 EnterCriticalSection 和 WaitForSingleObject 的争用情况将有所帮

转载请注明: 转载自iT人 – theiter

本文链接地址: WaitForSingleObject 与 EnterCriticalSection 性能比较

随机日志