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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/800383/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 17:14:30  来源:igfitidea点击:

What is the difference between mutex and critical section?

windowslinuxmultithreadingprogramming-languages

提问by

Please explain from Linux, Windows perspectives?

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

I am programming in C#, would these two terms make a difference. Please post as much as you can, with examples and such....

我正在用 C# 编程,这两个术语会有所不同吗?请尽可能多地发布,并附上示例等......

Thanks

谢谢

采纳答案by Michael

For Windows, critical sections are lighter-weight than mutexes.

对于 Windows,临界区比互斥体更轻。

Mutexes can be shared between processes, but always result in a system call to the kernel which has some overhead.

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

Critical sections can only be used within one process, but have the advantage that they only switch to kernel mode in the case of contention - Uncontended acquires, which should be the common case, are incredibly fast. In the case of contention, they enter the kernel to wait on some synchronization primitive (like an event or semaphore).

临界区只能在一个进程中使用,但其优点是它们仅在发生争用时才切换到内核模式 - 非争用获取(这应该是常见情况)非常快。在争用的情况下,它们进入内核以等待某些同步原语(如事件或信号量)。

I wrote a quick sample app that compares the time between the two of them. On my system for 1,000,000 uncontended acquires and releases, a mutex takes over one second. A critical section takes ~50 ms for 1,000,000 acquires.

我编写了一个快速示例应用程序来比较两者之间的时间。在我的系统上进行 1,000,000 次无竞争获取和释放时,互斥量需要超过一秒钟。对于 1,000,000 次获取,关键部分需要大约 50 毫秒。

Here's the test code, I ran this and got similar results if mutex is first or second, so we aren't seeing any other effects.

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

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);

回答by Promit

In Windows, a critical section is local to your process. A mutex can be shared/accessed across processes. Basically, critical sections are much cheaper. Can't comment on Linux specifically, but on some systems they're just aliases for the same thing.

在 Windows 中,临界区对您的进程来说是本地的。可以跨进程共享/访问互斥锁。基本上,关键部分要便宜得多。无法具体评论 Linux,但在某些系统上,它们只是同一事物的别名。

回答by Zifre

A mutex is an object that a thread can acquire, preventing other threads from acquiring it. It is advisory, not mandatory; a thread can use the resource the mutex represents without acquiring it.

互斥量是一个线程可以获取的对象,阻止其他线程获取它。它是建议性的,而不是强制性的;线程可以使用互斥锁代表的资源而无需获取它。

A critical section is a length of code that is guaranteed by the operating system to not be interupted. In pseudo-code, it would be like:

临界区是由操作系统保证不被中断的一段代码。在伪代码中,它会是这样的:

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();

回答by The Unknown

Critical Section and Mutex are not Operating system specific, their concepts of multithreading/multiprocessing.

临界区和互斥体不是操作系统特定的,它们的多线程/多处理概念。

Critical SectionIs a piece of code that must only run by it self at any given time (for example, there are 5 threads running simultaneously and a function called "critical_section_function" which updates a array... you don't want all 5 threads updating the array at once. So when the program is running critical_section_function(), none of the other threads must run their critical_section_function.

临界区是一段只能在任何给定时间由它自己运行的代码(例如,有 5 个线程同时运行,一个名为“critical_section_function”的函数更新数组......你不想要所有 5 个线程立即更新数组。因此,当程序运行critical_section_function() 时,其他线程都不必运行它们的critical_section_function。

mutex*Mutex is a way of implementing the critical section code (think of it like a token... the thread must have possession of it to run the critical_section_code)

mutex*Mutex 是一种实现临界区代码的方式(把它想象成一个令牌……线程必须拥有它才能运行critical_section_code)

回答by 1800 INFORMATION

In addition to the other answers, the following details are specific to critical sections on windows:

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

  • in the absence of contention, acquiring a critical section is as simple as an InterlockedCompareExchangeoperation
  • the critical section structure holds room for a mutex. It is initially unallocated
  • if there is contention between threads for a critical section, the mutex will be allocated and used. The performance of the critical section will degrade to that of the mutex
  • if you anticipate high contention, you can allocate the critical section specifying a spin count.
  • if there is contention on a critical section with a spin count, the thread attempting to acquire the critical section will spin (busy-wait) for that many processor cycles. This can result in better performance than sleeping, as the number of cycles to perform a context switch to another thread can be much higher than the number of cycles taken by the owning thread to release the mutex
  • if the spin count expires, the mutex will be allocated
  • when the owning thread releases the critical section, it is required to check if the mutex is allocated, if it is then it will set the mutex to release a waiting thread
  • 在没有争用的情况下,获取临界区就像一个InterlockedCompareExchange操作一样简单
  • 临界区结构为互斥锁留有空间。它最初是未分配的
  • 如果临界区的线程之间存在争用,则将分配和使用互斥锁。临界区的性能将下降到互斥锁的性能
  • 如果您预计会出现高争用,您可以分配指定自旋计数的临界区。
  • 如果在具有自旋计数的临界区上存在争用,则尝试获取临界区的线程将自旋(忙等待)多个处理器周期。这可以导致比休眠更好的性能,因为执行上下文切换到另一个线程的周期数可能远高于拥有线程释放互斥锁所花费的周期数
  • 如果自旋计数到期,则将分配互斥锁
  • 当所属线程释放临界区时,需要检查互斥锁是否被分配,如果是则设置互斥锁释放一个等待线程

In linux, I think that they have a "spin lock" that serves a similar purpose to the critical section with a spin count.

在 linux 中,我认为它们有一个“自旋锁”,其作用与具有自旋计数的临界区相似。

回答by Daniel Brückner

From a theoretical perspective, a critical sectionis a piece of code that must not be run by multiple threads at once because the code accesses shared resources.

从理论上讲,临界区是一段不能由多个线程同时运行的代码,因为该代码访问共享资源。

A mutexis an algorithm (and sometimes the name of a data structure) that is used to protect critical sections.

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

Semaphoresand Monitorsare common implementations of a mutex.

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

In practice there are many mutex implementation availiable in windows. They mainly differ as consequence of their implementation by their level of locking, their scopes, their costs, and their performance under different levels of contention. See CLR Inside Out - Using concurrency for scalabilityfor an chart of the costs of different mutex implementations.

在实践中,windows 中有许多可用的互斥锁实现。它们的主要区别在于它们的锁定级别、范围、成本以及在不同争用级别下的性能。有关不同互斥体实现的成本图表,请参阅CLR Inside Out - Using concurrency forscaling。

Availiable synchronization primitives.

可用的同步原语。

The lock(object)statement is implemented using a Monitor- see MSDNfor reference.

lock(object)语句是使用 a 实现的Monitor- 请参阅MSDN以供参考。

In the last years much research is done on non-blocking synchronization. The goal is to implement algorithms in a lock-free or wait-free way. In such algorithms a process helps other processes to finish their work so that the process can finally finish its work. In consequence a process can finish its work even when other processes, that tried to perform some work, hang. Usinig locks, they would not release their locks and prevent other processes from continuing.

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

回答by Martin

Just to add my 2 cents, critical Sections are defined as a structure and operations on them are performed in user-mode context.

补充一下我的 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

Whereas mutex are kernel objects (ExMutantObjectType) created in the Windows object directory. Mutex operations are mostly implemented in kernel-mode. For instance, when creating a Mutex, you end up calling nt!NtCreateMutant in kernel.

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

回答by Tim Post

The 'fast' Windows equal of critical selection in Linux would be a futex, which stands for fast user space mutex. The difference between a futex and a mutex is that with a futex, the kernel only becomes involved when arbitration is required, so you save the overhead of talking to the kernel each time the atomic counter is modified. That .. can save a significantamount of time negotiating locks in some applications.

与 Linux 中的关键选择相同的“快速”Windows 将是futex,它代表快速用户空间互斥锁。futex 和 mutex 之间的区别在于,使用 futex,内核仅在需要仲裁时才参与,因此您可以节省每次修改原子计数器时与内核对话的开销。这..可以节省显著在某些应用时间谈判锁的数量。

A futex can also be shared amongst processes, using the means you would employ to share a mutex.

futex 也可以在进程之间共享,使用共享互斥锁的方法。

Unfortunately, futexes can be very tricky to implement(PDF). (2018 update, they aren't nearly as scary as they were in 2009).

不幸的是,futex 实现起来可能非常棘手(PDF)。(2018 年更新,它们几乎不像 2009 年那么可怕)。

Beyond that, its pretty much the same across both platforms. You're making atomic, token driven updates to a shared structure in a manner that (hopefully) does not cause starvation. What remains is simply the method of accomplishing that.

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

回答by Stevens Miller

Great answer from Michael. I've added a third test for the mutex class introduced in C++11. The result is somewhat interesting, and still supports his original endorsement of CRITICAL_SECTION objects for single processes.

迈克尔的精彩回答。我为 C++11 中引入的互斥类添加了第三个测试。结果有点意思,仍然支持他原来对单进程CRITICAL_SECTION对象的认可。

mutex m;
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);

// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    m.lock();
    m.unlock();
}

QueryPerformanceCounter(&end);

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


printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);

My results were 217, 473, and 19 (note that my ratio of times for the last two is roughly comparable to Michael's, but my machine is at least four years younger than his, so you can see evidence of increased speed between 2009 and 2013, when the XPS-8700 came out). The new mutex class is twice as fast as the Windows mutex, but still less than a tenth the speed of the Windows CRITICAL_SECTION object. Note that I only tested the non-recursive mutex. CRITICAL_SECTION objects are recursive (one thread can enter them repeatedly, provided it leaves the same number of times).

我的结果是 217、473 和 19(请注意,我最后两次的时间比与迈克尔的大致相当,但我的机器至少比他年轻四岁,因此您可以看到 2009 年到 2013 年之间速度提高的证据,当 XPS-8700 出来时)。新的互斥体类的速度是 Windows 互斥体的两倍,但仍然不到 Windows CRITICAL_SECTION 对象速度的十分之一。请注意,我只测试了非递归互斥锁。CRITICAL_SECTION 对象是递归的(一个线程可以重复进入它们,前提是它离开的次数相同)。

回答by Enrico Migliore

A C functions is called reentrant if it uses its actual parameters only.

如果 AC 函数仅使用其实际参数,则称为可重入函数。

Reentrant functions can be called by multiple threads at the same time.

可重入函数可以被多个线程同时调用。

Example of reentrant function:

重入函数示例:

int reentrant_function (int a, int b, int *result)
{
   int c;

   c = a + b;

   *result = c;

   return 0;
}

Example of non reentrant function:

不可重入函数示例:

int result;

int reentrant_function (int a, int b, int *result)
{
   int c;

   c = a + b;

   result = c;

   return 0;
}

The C standard library strtok() is not reentrant and can't be used by 2 or more threads at the same time.

C 标准库 strtok() 不可重入,不能同时被 2 个或更多线程使用。

Some platform SDK's comes with the reentrant version of strtok() called strtok_r();

某些平台 SDK 带有 strtok() 的可重入版本,称为 strtok_r();

Enrico Migliore

恩里科·米格里奥