互斥和临界区有什么区别?

130 linux windows multithreading programming-languages

请从Linux,Windows的角度解释一下?

我用C#编程,这两个术语会有所不同.请尽可能多地发布,举例等等......

谢谢

Mic*_*ael 222

对于Windows,关键部分比互斥体重量更轻.

互斥体可以在进程之间共享,但总是会导致对内核的系统调用,这会产生一些开销.

关键部分只能在一个进程中使用,但具有以下优点:它们仅在争用情况下切换到内核模式 - 非常见的获取(应该是常见情况)非常快.在争用的情况下,它们进入内核以等待一些同步原语(如事件或信号量).

我写了一个快速的示例应用程序,比较两者之间的时间.在我的系统上,1,000,000个无争用的获取和释放,互斥量需要一秒钟.1,000,000个采集的关键部分需要约50毫秒.

这是测试代码,如果互斥是第一个或第二个,我运行它并得到类似的结果,所以我们没有看到任何其他效果.

HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);
Run Code Online (Sandbox Code Playgroud)

  • @TroyHoward你不是基本上只是旋转锁定吗? (5认同)
  • @TroyHoward尝试强制你的CPU一直以100%运行,看看INFINITE是否运行得更好.我的机器(Dell XPS-8700)上的电源策略可能需要长达40毫秒才能在它决定减速后爬回到全速,如果您睡觉或等待一毫秒,它可能无法完成. (2认同)

Dan*_*ner 85

从理论的角度来看,关键部分是一段代码,由于代码访问共享资源,因此不能同时由多个线程运行.

互斥是用来保护临界区的算法(和有时的数据结构的名称).

信号量监视器是互斥锁的常见实现.

实际上,在Windows中可以使用许多互斥实现.它们的主要不同之处在于它们的锁定水平,范围,成本以及在不同争用水平下的表现.请参阅CLR Inside Out - 将并发性用于可伸缩性,以获取不同互斥实现的成本图表.

可用的同步原语.

lock(object)声明使用Monitor- 参见MSDN实现.

在过去几年中,对非阻塞同步进行了大量研究.目标是以无锁或无等待的方式实现算法.在这样的算法中,过程有助于其他过程完成其工作,以便过程最终完成其工作.因此,即使当试图执行某些工作的其他进程挂起时,进程也可以完成其工作.Usinig锁定,他们不会释放锁定并阻止其他进程继续.

  • _Practical_无锁编程类似于香格里拉,只不过它存在。Keir Fraser的[论文](http://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf)(PDF)对此进行了有趣的探讨(可追溯至2004年)。而且在2012年,我们仍在为此而苦苦挣扎。 (2认同)

180*_*ION 21

除了其他答案之外,以下详细信息特定于Windows上的关键部分:

  • 在没有争用的情况下,获取关键部分就像InterlockedCompareExchange操作一样简单
  • 临界区结构为互斥锁提供了空间.它最初是未分配的
  • 如果临界区的线程之间存在争用,则将分配和使用互斥锁.关键部分的性能将降低到互斥锁的性能
  • 如果您预计会出现高争用,则可以分配指定旋转计数的临界区.
  • 如果在具有旋转计数的关键部分上存在争用,则尝试获取临界区的线程将旋转(忙等待)那么多处理器周期.这可以导致比休眠更好的性能,因为执行上下文切换到另一个线程的周期数可以远远高于拥有线程释放互斥锁所花费的周期数
  • 如果旋转计数到期,则将分配互斥锁
  • 当拥有线程释放临界区时,需要检查互斥锁是否已分配,如果是,则会设置互斥锁以释放等待线程

在linux中,我认为他们有一个"旋转锁定",它与具有旋转计数的关键部分具有相似的目的.

  • 这绝对不是真的。CAS 可以在用户模式下使用 cmpxchg 完成。 (2认同)

The*_*own 18

Critical Section和Mutex不是特定于操作系统的,它们是多线程/多处理的概念.

关键部分 是一段代码,必须在任何给定时间自行运行(例如,有5个线程同时运行,而一个名为"critical_section_function"的函数更新数组...你不想要所有5个线程一次更新数组.因此,当程序运行critical_section_function()时,其他任何线程都不能运行其critical_section_function.

mutex* Mutex是一种实现关键部分代码的方式(将其视为一个令牌......线程必须拥有它才能运行critical_section_code)

  • 此外,可以跨进程共享互斥锁. (2认同)

Tim*_*ost 14

Linux中"快速"Windows等级的关键选择将是一个futex,代表快速用户空间互斥.futex和互斥锁之间的区别在于,使用futex时,只有在需要仲裁时才会涉及内核,因此每次修改原子计数器时都可以节省与内核通信的开销.这..可以节省显著在某些应用时间谈判锁的数量.

还可以使用您用来共享互斥锁的方法在进程之间共享futex.

不幸的是,futex实现起来非常棘手(PDF).(2018年的更新,它们并不像2009年那样可怕).

除此之外,它在两个平台上几乎相同.您正在以一种(希望)不会导致饥饿的方式对共享结构进行原子,令牌驱动的更新.剩下的只是完成它的方法.


Zif*_*fre 13

互斥锁是线程可以获取的对象,阻止其他线程获取它.这是咨询,而不是强制性的; 线程可以使用互斥体所代表的资源而无需获取它.

关键部分是操作系统保证不会中断的一段代码.在伪代码中,它会像:

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();
Run Code Online (Sandbox Code Playgroud)

  • 我认为海报是在谈论用户模式同步原语,比如一个 win32 临界区对象,它只是提供互斥。我不了解 Linux,但 Windows 内核具有像您描述的那样行为的关键区域 - 不可中断。 (2认同)
  • 我不知道你为什么被否决了。有一个临界区的 _concept_,您已经正确描述了它,它不同于称为 CriticalSection 的 Windows 内核对象,后者是一种互斥锁。我相信 OP 是在询问后一个定义。 (2认同)

Pro*_*mit 6

在Windows中,关键部分是您的流程的本地部分.可以跨进程共享/访问互斥锁.基本上,关键部分要便宜得多.不能专门评论Linux,但在某些系统上,它们只是同一个东西的别名.


小智 6

只是为了增加我的2美分,关键部分被定义为结构,并且在它们上的操作在用户模式上下文中执行.

ntdll!_RTL_CRITICAL_SECTION
   +0x000 DebugInfo        : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
   +0x004 LockCount        : Int4B
   +0x008 RecursionCount   : Int4B
   +0x00c OwningThread     : Ptr32 Void
   +0x010 LockSemaphore    : Ptr32 Void
   +0x014 SpinCount        : Uint4B

而互斥是在Windows对象目录中创建的内核对象(ExMutantObjectType).互斥操作主要在内核模式下实现.例如,在创建Mutex时,最终会在内核中调用nt!NtCreateMutant.

  • 内核对象具有引用计数.关闭对象的句柄会减少引用计数,当它达到0时,将释放该对象.当进程崩溃时,其所有句柄都会自动关闭,因此只有该进程具有句柄的互斥锁才会自动解除分配. (6认同)