我正在调试一个多线程应用程序,发现了内部结构 CRITICAL_SECTION. 。我找到了数据成员 LockSemaphore CRITICAL_SECTION 的一个有趣的部分。

看起来像 LockSemaphore 是一个自动重置事件(顾名思义,不是信号量),当线程第一次等待时,操作系统会默默地创建此事件 Critcal Section 它被其他线程锁定。

现在,我想知道关键部分总是更快吗?事件是一个内核对象,每个关键部分对象都与事件对象关联,那么如何 Critical Section 与 Mutex 等其他内核对象相比可以更快吗?另外,内部事件对象实际上如何影响关键部分的性能?

这是结构 CRITICAL_SECTION:

struct RTL_CRITICAL_SECTION
{
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;
    HANDLE LockSemaphore;
    ULONG_PTR SpinCount;
};
有帮助吗?

解决方案

当他们说关键部分“快”时,他们的意思是“当它尚未被另一个线程锁定时获取它很便宜”。

[请注意,如果 已经被另一个线程锁定,那么它的速度有多快并不重要。]

它之所以快的原因是,在进入内核之前,它使用了相当于 InterlockedIncrement 在其中之一上 LONG 场(也许在 LockCount 字段),如果成功,则它认为在没有进入内核的情况下获得了锁。

InterlockedIncrement 我认为API是在用户模式下作为“LOCK INC”操作码实现的......换句话说,您可以获取一个无争议的临界区,而无需进行任何到内核的环转换。

其他提示

在性能工作中,很少有东西属于“总是”类别:)如果您自己使用其他原语实现类似于操作系统关键部分的东西,那么在大多数情况下,速度会更慢。

回答您的问题的最佳方法是性能测量。操作系统对象的执行方式是 非常 取决于场景。例如,如果争用较低,则关键部分通常被认为是“快”。如果锁定时间小于自旋计数时间,它们也被认为是快速的。

要确定的最重要的事情是关键部分的争用是否是应用程序中的首要限制因素。如果没有,那么只需正常使用关键部分并解决应用程序的主要瓶颈(或瓶颈)。

如果关键部分的性能至关重要,那么您可以考虑以下事项。

  1. 仔细设置“热门”关键部分的自旋锁计数。如果性能至关重要,那么这里的工作是值得的。请记住,虽然自旋锁确实避免了用户模式到内核的转换,但它会以惊人的速度消耗 CPU 时间 - 在自旋时,没有其他任何东西可以使用该 CPU 时间。如果锁持有的时间足够长,那么旋转线程实际上会阻塞,从而释放 CPU 来执行其他工作。
  2. 如果您有读取器/写入器模式,请考虑使用 超薄读/写器 (SRW) 锁. 。缺点是它们仅适用于 Vista 和 Windows Server 2008 及更高版本的产品。
  3. 您也许可以使用 条件变量 使用关键部分来最大程度地减少轮询和争用,仅在需要时唤醒线程。同样,这些在 Vista 和 Windows Server 2008 及更高版本的产品上受支持。
  4. 考虑使用 互锁单链表 (SLIST)-这些是高效且“无锁”的。更好的是,它们在 XP 和 Windows Server 2003 及更高版本的产品上受支持。
  5. 检查您的代码 - 您也许可以通过重构某些代码并使用互锁操作或 SLIST 进行同步和通信来打破“热”锁。

总之,调整存在锁争用的场景可能是一项具有挑战性(但很有趣!)的工作。专注于测量应用程序性能并了解热路径在哪里。xperf 工具位于 Windows 性能工具包 是你的朋友吗:)我们刚刚发布了适用于 Windows 7 和 .NET Framework 3.5 SP1 的 Microsoft Windows SDK 4.5 版本(ISO在这里, 网络安装程序在这里)。您可以找到 xperf 工具的论坛 这里. 。V4.5完全支持Win7、Vista、Windows Server 2008 - 所有版本。

CriticalSections速度较快,但 InterlockedIncrement / InterlockedDecrement 是更。看到该实现使用样品 LightweightLock完整副本

在CriticalSections会旋转一小段时间(数毫秒)和保持检查如果锁是免费的。后旋算“超时”,那么它会回落到内核事件。所以在锁的持有人迅速失控的情况下,你从来没有让昂贵的过渡到内核代码。

编辑:去了,发现在我的代码一些注释:显然MS堆管理器使用的4000自旋数(整数增量,而不是MS)

下面是一个方式来看待它:

如果没有竞争,那么自旋锁非常快相比,去到内核模式的互斥。

当有争用,CriticalSection的是比直接使用一个互斥(因为额外的工作以检测所述自旋锁状态)稍贵。

所以它归结为一个加权平均,其中权重取决于您的通话模式的具体细节。话虽这么说,如果你没有什么竞争,那么CriticalSection的是巨大的胜利。如果,另一方面,你一直有很多争论,那么你会付出很小的罚款相对于直接使用互斥。但是,在这种情况下,你会切换到一个互斥增益较小,所以你可能会更好试图减少竞争。

关键部分比互斥更快为什么因为临界区是不是一个内核对象。这是当前进程的全局内存的一部分。实际上互斥驻留在内核和mutext对象的创建需要一个内核切换,但关键部分的情况下不得。尽管关键部分是快速,会出现在使用临界区时,线程要等待状态的内核切换。这是因为线程调度在内核侧发生。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top