线程锁定

当等待线程连接停止时,例如 关键分区对象 共享资源的访问权限时,会出现线程锁定。 如果两个线程尝试同时访问共享资源,则其中一个线程将处于挂起状态。 (有关详细信息,请参阅 线程状态。)挂起的会话在共享资源可用前无法继续运行。

PIX

在 PIX 的"日程表"窗格中,您可以通过观察线程何时运行、线程在哪个核心正在运行以及会话被切换的原因来查看会话锁定位置。

确定线程在代码中的锁定位置

  1. 执行常见步骤以生成计时捕获。

    图 1. PIX 中显示计时捕获的日程表窗格

    PIX 中

    图 1 中显示的每个红色刻度线表示上下文切换。 红色部分较粗时,多个上下文切换会接近一起。

  2. 放大包含多个上下文切换的感兴趣的区域。

    图 2:PIX 中"日程表"窗格的展开部分

    PIX 中

    注意

    可以选择每个时间线标签左侧的齿轮图标以显示视图选项,例如增强上下文切换使其更易于查看。

    在图 2 中,请注意每个会话日程表上栏的垂直明亮红色线条。 每行表示上下文开关。

  3. 将鼠标悬停在上下文开关上以显示有关它的基本信息。 如果在上下文切换开始运行的会话由另一个线程放入就绪状态,则两个会话日程表之间将出现一个箭头,如图 3 所示。

    图 3. PIX 中显示上下文切换详细信息的日程表窗格

    PIX 中

  4. 单击上下文开关的行以将其选中。 现在查看"日程表"选项卡左下角的"元素详细信息"窗格,图 4 中所示。 该窗格显示会话切换(从)、会话切换()以及 准备的会话(如果可用)。 在这种情况下,上下文切换从空闲到线程 792,并且由线程 756 置入就绪状态。

    图 4. 显示调用堆栈的上下文开关的 PIX 元素详细信息窗格中的"元素详细信息"窗格

    PIX 中

    调用堆栈中,你可以看到线程 792 从对 ATG::EventLockable 对象的调用恢复为 lock,这是 Windows 事件对象的一个主题。 名为"正在准备的会话 756 unlock 处理同一对象。 这两个线程称为同一 AcquireLockAndSpin 函数。 此示例中,线程 756 为上下文切换设置了 Windows 事件,会话 792 将等待该事件。

    注意

    并非所有上下文切换都有“随时可用”的数据。 例如,从睡眠状态启动的会话在上下文切换中未列出"已准备"会话。

WPA

也可使用 Windows Performance Analyzer (WPA) 来查找线程锁定位置。 用于发现锁定线程的两种最佳视图是按进程列出的 CPU 使用率(精确)日程表、线程CPU 使用率(精确)日程表(按 CPU),这两个都使用名为 ThreadLocking.wpaProfile的 WPA 配置文件。 这些视图以上下文切换为基础,并且准确地显示线程在什么时候退出,以及新线程的调用堆栈。

  1. 执行 步骤 事件跟踪日志 (ETL) 文件。

  2. ThreadLocking.wpaProfile WPA 配置文件。 新的"分析"选项卡应类似图 5。

    图 5. WPA 中的"线程锁定"配置文件的默认视图

    WPA 中的

    这两种视图只是查看相同数据的不同方法。 取决于该问题是否看起来与特定线程相关,还是与 CPU 核心相关,每个视图可以有其优势。 对于本示例,我们仅使用" "进程,线程“ 视图。 在此示例中,我们就只使用进程、线程 视图,但如果问题更普遍或已本地化为 CPU 内核,则可以在 CPU 视图中使用相同步骤。

  3. 选择所关注的线程。 该线程运行时的所有时间段将在图表中突出显示。

  4. 放大到时间线的某一部分。 这有助于删除可能正在运行的其他线程的噪音。 一个不错的起点是单帧。

  5. 查找一个在切换时间过长后开始运行的会话的位置,或者启动并停止大量次数的会话。 放大到该位置,图 6 所示。

    图 6. 放大显示有线程从正在处理中切换出的时间框架

    WPA 中显示五个线程处理的视图的屏幕截图

  6. 选择图形中的特定线索或时间范围以突出显示表中的匹配部分,如下图 7 所示。 新线程堆栈(帧标记)列将显示之前已换出线程的代码中的调用堆栈,而 计数列将显示在时间线视图当前可见的线程部分中,该调用堆栈帧出现在所有上下文切换的次数。

    图 7. 线程 1084 的会话日程表显示它已切换四次,因为 EventLockable::lock 名为 WaitForSingleObjectEx

    显示会话 1084 何时和为什么切换的会话日程表的屏幕截图

  7. 由于各种原因,可以切换会话。 放大各种位置和线程可以更清晰地了解代码发生的情况。 例如,会话 1084 的四个上下文开关分别在堆叠结尾的数据表向下单独列出。 通过调整列的宽度,我们可以在 准备好线程堆栈 列(图 8)中查看信息,该列显示用于特定上下文切换的准备就绪线程。

    图 8. WPA 数据表显示线程 1084 的一个上下文切换通过线程 1076 到 ATG::EventLockable::unlock

    WPA 中显示一个上下文开关的调用堆栈的数据表的屏幕截图

锁定线程的常见原因

有多种原因可能会锁定线程。 以下是一些最常见的原因以及需要执行哪些处理。

  • 在自旋锁中使用 SwitchToThread 的紧密循环
    • 自旋锁上的高度争用。
    • 请考虑扩展字数。
  • EnterCriticalSection 调用
    • 关键区有高度争用。
    • 请考虑扩展字数。
  • WaitForSingleObject
    • 线程通信太高。
      • 考虑另一种不需要原语的算法。
      • 批处理消息以最小化通信。
    • 工作不足,无法执行。
      • 考虑增加作业的复杂性。
    • 在新作业就绪前即将完成的作业。
      • 请考虑在阻止前,在 WaitForSingleObject 自旋。
  • 优先级反转
    • 低优先级线程持有高优先级线程所需的锁定。