【问题标题】:Are mutexes really slower?互斥锁真的慢吗?
【发布时间】:2009-11-03 11:01:37
【问题描述】:

我在网上和任何地方都读过很多次,说互斥锁比临界区/信号量/insert-your-preferred-synchronisation-method-here 慢。但我从未见过任何论文或研究或任何支持这种说法的东西。

那么,这个想法从何而来?这是神话还是现实?互斥体真的慢吗?

【问题讨论】:

  • 如果没有更多上下文,这个问题就没有多大意义?
  • 它非常通用...作为 Windows 开发人员,我对 Windows 的答案很感兴趣,但我也想知道不同操作系统或体系结构之间是否存在差异。潜在的问题是:如果我必须选择一种同步机制,我会使用互斥体还是应该首先考虑其他机制? (实际上,我使用互斥锁或二进制信号量没有尝试其他任何东西)
  • windows 中的临界区是自旋锁和信号量之间的权衡。
  • 好问题! .... :-)

标签: windows multithreading mutex


【解决方案1】:

在 Jim Beveridge 和 Robert Wiener 所著的《Win32 中的多线程应用程序》一书中,它说:“锁定无主互斥体所需的时间几乎是锁定无主临界区的 100 倍,因为临界区可以在不涉及内核的用户模式”

在 msdn here 上,它说“临界区对象为互斥同步提供了一种稍快、更有效的机制”

【讨论】:

  • 那么临界区有什么缺点吗?我真的是一名 Java 程序员,在 Java 中这两个术语是同义词。
  • 在 Java 中,一切都很慢 ;-)
  • @Benj:我认为你不能在 Java 中使用本机 Win32 临界区对象,因为一旦初始化,临界区数据结构就不能在内存中移动。即它不会在压缩 GC 中存活,并且不能通过值传递。
  • @Benj -- 因为临界区不是内核对象,所以它只能在单个进程中工作。如果您需要跨多个进程同步访问,那么互斥锁是您的选择。
【解决方案2】:

我不认为任何答案都触及了它们为何不同的关键点。

互斥锁处于操作系统级别。存在一个命名的互斥锁,并且可以从操作系统中的任何进程访问(只要它的 ACL 允许从所有进程访问)。

关键部分更快,因为它们不需要系统调用进入内核模式,但是它们只能一个进程中工作,您不能使用一个关键部分锁定多个进程。因此,根据您要实现的目标以及软件设计的外观,您应该为工作选择最合适的工具。

我还要向您指出,信号量与互斥体/关键部分是分开的,因为它们的数量。信号量可用于控制对资源的多个并发访问,其中互斥体/关键部分要么被访问,要么未被访问。

【讨论】:

  • +1 指出使用这两种结构实现的同步的不同性质。
【解决方案3】:

CRITICAL_SECTION 被实现为具有上限自旋计数的自旋锁。请参阅MSDN InitializeCriticalSectionAndSpinCount 以获取此指示。

当自旋计数“过去”时,临界区会锁定一个信号量(或实现它的任何内核锁)。

所以在代码中它是这样工作的(不是真的工作,应该只是一个例子):

CRITICAL_SECTION s;

void EnterCriticalSection( CRITICAL_SECTION* s )
{
    int spin_count = s.max_count;
    while( --spin_count >= 0 )
    {
        if( InterlockedExchange( &s->Locked, 1 ) == 1 )
        {
           // we own the lock now
           s->OwningThread = GetCurrentThread();
           return;
        }
    }
    // lock the mutex and wait for an unlock
    WaitForSingleObject( &s->KernelLock, INFINITE );
}

因此,如果您的临界区只保留很短的时间,并且进入的线程只等待很少的“旋转”(循环),那么临界区可能非常有效。但如果不是这样,临界区会浪费很多周期什么都不做,然后回退到内核同步对象。

所以权衡是:

互斥体: 缓慢的获取/释放,但不会浪费长“锁定区域”的周期

CRITICAL_SECTION:快速获取/释放未拥有的“区域”,但浪费了拥有部分的周期。

【讨论】:

  • 实际上,与 EnterCriticalSections 旋转所花费的相比,互斥体浪费的上下文切换周期更多。这就是旋转的意义所在。
  • 在最坏的情况下,每个 EnterCriticalSection 都需要 spin_count 个周期 + WaitForSingleObject 的周期。
  • MSDN 说单处理器系统上的自旋计数始终为零...这是否意味着临界区与这些处理器上的互斥锁相同?
  • 不完全。您可以轻松检查当前是否有另一个线程正在尝试获取锁,因为在任何给定时间只有一个线程可以运行。所以,如果它被锁定了,我们可以直接去互斥体让我们的线程进入休眠状态。如果它没有被锁定,我们会得到与多核系统相同的行为。
  • 你是对的,我误读了你的伪代码:内核对象获取只有在临界区已经被锁定时才会发生。
【解决方案4】:

是的,关键部分更有效。要获得很好的解释,请参阅“Windows 上的并发编程”。

简而言之:互斥体是一个内核对象,所以当你获得一个互斥体时总会有一个上下文切换,即使是“免费的”。在这种情况下,可以在没有上下文切换的情况下获取临界区,并且(在多核/处理器机器上)如果它被阻止以防止代价高昂的上下文切换,它甚至会旋转几个周期。

【讨论】:

    【解决方案5】:

    互斥体(至少在 Windows 中)允许除了线程之外的不同进程之间的同步。这意味着必须做额外的工作来确保这一点。此外,作为Brian pointed out,使用互斥体还需要切换到“内核”模式,这会导致另一个速度下降(我相信,即推断,此进程间同步需要内核,但是我没有什么可以支持我的)。

    编辑:您可以找到对进程间同步here 的明确引用,有关此主题的更多信息,请查看Interprocess Synchronization

    【讨论】:

    • 需要内核模式,因为只有在内核模式下才能保证互斥对象不会被其他线程写入。此外,由于这是在进程之间共享的,因此实现互斥锁的唯一安全方法是在通过内核进行安全检查之后。最后,只有内核可以控制 IO 等待队列,这是线程将被放置的位置,以便等待获取互斥锁。
    猜你喜欢
    • 2018-05-23
    • 1970-01-01
    • 2015-07-19
    • 2011-07-14
    • 1970-01-01
    • 1970-01-01
    • 2015-10-11
    • 2012-06-05
    • 2010-09-16
    相关资源
    最近更新 更多