我正在使用C#进行编程,这两个术语会有什么区别。请尽可能详细地说明,包括例子等等......
谢谢
对于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);
从理论上讲,关键区域是一段代码,由于该代码访问共享资源,不应被多个线程同时运行。
互斥锁是一种算法(有时也是一种数据结构的名称),用于保护关键区域。
实际上,在Windows中有许多互斥锁的实现可用。它们的差异主要取决于它们的锁定级别、范围、成本以及在不同竞争级别下的性能。请参见CLR Inside Out - 使用并发实现可扩展性,了解不同互斥锁实现的成本图表。
可用的同步原语:
lock(object)
语句是使用Monitor
实现的 - 参见MSDN了解详情。
近年来,关于非阻塞同步进行了大量研究。其目标是以无锁或无等待方式实现算法。在这种算法中,一个进程可以帮助其他进程完成它们的工作,以便该进程最终可以完成它的工作。因此,即使其他进程试图执行某些工作而挂起,该进程也可以完成其工作。使用锁会导致它们不释放其锁定并阻止其他进程继续执行。
InterlockedCompareExchange
操作一样简单。在Linux中,我认为他们有一个“自旋锁”,其作用类似于具有自旋计数的关键部分。
关键区域(Critical Section)和互斥锁(Mutex)并不是操作系统特定的,它们属于多线程/多进程的概念。
关键区域 是一段代码,要求在任何给定的时间内只能由其本身运行(例如,有5个线程同时运行,有一个名为“critical_section_function”的函数更新了一个数组...你不想让这5个线程同时更新该数组。所以当程序运行critical_section_function()时,其他线程都不能运行其critical_section_function。
互斥锁* 是实现关键区域代码的一种方式(把它看作是一个令牌...线程必须拥有该令牌才能运行关键区域代码)
一个互斥锁是一个对象,一个线程可以获取它,防止其他线程获取它。它是咨询性的,而不是强制性的;一个线程可以在不获取互斥锁的情况下使用表示互斥锁的资源。
一个临界区是一段代码,在操作系统保证不会被中断。在伪代码中,它可能像这样:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
补充一下,关键段被定义为一种结构,在用户模式上执行它们的操作。
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
而互斥对象是内核对象(ExMutantObjectType),创建在Windows对象目录中。互斥操作大多在内核模式下实现。例如,创建一个互斥对象时,最终会在内核中调用nt!NtCreateMutant。
Michael的回答很好。我为C ++ 11中介绍的mutex类添加了第三个测试。结果有些有趣,并且仍然支持他对单进程使用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);
关键区域的实现可能比普通内核互斥锁更有效。第一个答案中给出的示例有点误导人,因为它没有描述同步原语的设计目的:同步多个线程对某个东西的访问。该示例仅测量了临界区/互斥锁从未被另一个线程拥有的简单情况。 虽然如果两个线程在短时间内交替访问数据,则关键区域可能更有效,但如果我们有大量线程访问相同的数据,则可能会证明效率较低。每个线程都会自旋锁定,直到放弃并等待信号量,这是关键区域实现的一部分。 在测量执行时间时,也应考虑这种情况。