请从Linux,Windows的角度解释一下?
我在C#编程,这两个术语会有所不同吗?请尽可能多地发布,举例等等....
由于
答案 0 :(得分:218)
对于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);
答案 1 :(得分:83)
从理论角度来看,critical section是一段代码,由于代码访问共享资源,因此不能同时由多个线程运行。
mutex是一种算法(有时是数据结构的名称),用于保护关键部分。
Semaphores和Monitors是互斥锁的常见实现。
在实践中,Windows中有许多可用的互斥体实现。它们的主要不同之处在于它们的锁定水平,范围,成本以及在不同争用水平下的表现。有关不同互斥实现的成本图表,请参阅CLR Inside Out - Using concurrency for scalability。
可用的同步原语。
lock(object)
语句是使用Monitor
实现的 - 请参阅MSDN以供参考。
在过去几年中,对non-blocking synchronization进行了大量研究。目标是以无锁或无等待的方式实现算法。在这样的算法中,过程有助于其他过程完成其工作,以便过程最终完成其工作。因此,即使当试图执行某些工作的其他进程挂起时,进程也可以完成其工作。 Usinig锁定,他们不会释放锁定并阻止其他进程继续。
答案 2 :(得分:20)
除了其他答案外,以下详细信息仅适用于Windows上的关键部分:
InterlockedCompareExchange
操作一样简单在linux中,我认为他们有一个“旋转锁定”,它与具有旋转计数的关键部分具有相似的目的。
答案 3 :(得分:18)
Critical Section和Mutex不是特定于操作系统的,它们是多线程/多处理的概念。
关键部分 是一段代码,只能在任何给定时间由它自己运行(例如,有5个线程同时运行,而一个名为“critical_section_function”的函数更新数组...你不希望所有5个线程都更新一次运行数组。所以当程序运行critical_section_function()时,其他任何线程都不能运行他们的critical_section_function。
<强>互斥* 强> Mutex是一种实现关键部分代码的方式(想象它就像一个令牌......线程必须拥有它才能运行critical_section_code)
答案 4 :(得分:14)
Linux中“快速”Windows等级的关键选择将是futex,代表快速用户空间互斥。 futex和互斥锁之间的区别在于,使用futex时,只有在需要仲裁时才会涉及内核,因此每次修改原子计数器时都可以节省与内核通信的开销。那个..可以节省在一些应用程序中协商锁的重要的时间。
还可以使用您用来共享互斥锁的方式在进程之间共享futex。
不幸的是,futex可以是very tricky to implement(PDF)。 (2018年的更新,它们并不像2009年那样可怕)。
除此之外,它在两个平台上几乎相同。您正在以一种(希望)不会导致饥饿的方式对共享结构进行原子,令牌驱动的更新。剩下的只是实现这一目标的方法。
答案 5 :(得分:13)
互斥锁是线程可以获取的对象,阻止其他线程获取它。这是咨询,而不是强制性的;线程可以使用互斥体所代表的资源而无需获取它。
关键部分是操作系统保证不会中断的一段代码。在伪代码中,它就像:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
答案 6 :(得分:6)
在Windows中,关键部分是您的流程的本地部分。可以跨进程共享/访问互斥锁。基本上,关键部分要便宜得多。不能专门评论Linux,但在某些系统上,它们只是同一个东西的别名。
答案 7 :(得分: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。
答案 8 :(得分:1)
迈克尔的好回答。我为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);
我的结果是217,473和19(请注意,我的最后两次的比率大致相当于迈克尔的,但我的机器比他的年轻至少四岁,所以你可以看到两者之间速度增加的证据2009年和2013年,当XPS-8700问世时。新的互斥锁类的速度是Windows互斥锁的两倍,但仍然不到Windows CRITICAL_SECTION对象速度的十分之一。请注意,我只测试了非递归互斥锁。 CRITICAL_SECTION对象是递归的(一个线程可以重复输入它们,只要它们保留相同的次数)。
答案 9 :(得分:-1)
如果C函数仅使用其实际参数,则称为可重入函数。
可重入函数可以同时被多个线程调用。
可重入函数示例:
int reentrant_function (int a, int b)
{
int c;
c = a + b;
return c;
}
不可重入函数的示例:
int result;
void non_reentrant_function (int a, int b)
{
int c;
c = a + b;
result = c;
}
C标准库strtok()
不可重入,并且不能同时被2个或更多线程使用。
某些平台SDK附带了strtok()
的可重入版本,称为strtok_r()
;