互斥锁和临界区有什么区别?
- 2024-10-22 08:29:00
- admin 原创
- 95
问题描述:
请从 Linux、Windows 的角度解释一下?
我使用 C# 编程,这两个术语会有什么区别吗?请尽可能多地发布内容,并附上示例等……
谢谢
解决方案 1:
对于 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
", totalTime, totalTimeCS);
解决方案 2:
从理论角度来看,关键部分是一段不能由多个线程同时运行的代码,因为该代码访问共享资源。
互斥锁是一种用于保护关键部分的算法(有时是数据结构的名称)。
信号量和监视器是互斥锁的常见实现。
实际上,Windows 中有许多可用的互斥锁实现。它们的主要区别在于其实现的锁定级别、范围、成本以及在不同争用级别下的性能。请参阅CLR Inside Out - 使用并发性实现可扩展性,了解不同互斥锁实现的成本图表。
可用的同步原语。
监视器
互斥锁
信号
读写锁
读写锁Slim
互锁
该lock(object)
语句是使用Monitor
--请参阅MSDN以供参考实现的。
近年来,人们对非阻塞同步进行了大量研究。目标是以无锁或无等待的方式实现算法。在这种算法中,一个进程帮助其他进程完成其工作,以便该进程最终完成其工作。因此,即使其他试图执行某些工作的进程挂起,进程也可以完成其工作。使用锁,它们不会释放锁并阻止其他进程继续运行。
解决方案 3:
除了其他答案之外,以下详细信息特定于 Windows 上的关键部分:
在没有争用的情况下,获取关键部分就像
InterlockedCompareExchange
操作一样简单临界区结构为互斥锁保留空间。它最初未分配
如果线程之间争用某个关键部分,则将分配并使用互斥锁。关键部分的性能将降低到互斥锁的性能
如果您预计竞争激烈,则可以分配指定旋转次数的临界区。
如果对具有旋转计数的临界区存在争用,则尝试获取临界区的线程将旋转(忙等待)那么多处理器周期。这可以带来比睡眠更好的性能,因为执行上下文切换到另一个线程的周期数可能比拥有线程释放互斥锁所需的周期数高得多
如果旋转计数到期,则将分配互斥锁
当拥有线程释放临界区时,需要检查互斥锁是否已分配,如果已分配,则将设置互斥锁以释放等待线程
在 Linux 中,我认为它们有一个“旋转锁”,其用途与具有旋转计数的临界区类似。
解决方案 4:
临界区和互斥锁不是操作系统特有的,它们是多线程/多处理的概念。
临界区
是一段在任意给定时间内只能自行运行的代码(例如,有 5 个线程同时运行,并且有一个名为“critical_section_function”的函数用于更新数组……您不希望所有 5 个线程同时更新数组。因此,当程序运行 critical_section_function() 时,其他线程都不必运行其 critical_section_function。
mutex*
Mutex 是实现临界区代码的一种方法(可以把它想象成一个令牌……线程必须拥有它才能运行 critical_section_code)
解决方案 5:
互斥锁是一个线程可以获取的对象,用于阻止其他线程获取它。它是建议性的,而不是强制性的;线程可以使用互斥锁所代表的资源而无需获取它。
临界区是操作系统保证不被中断的一段代码。用伪代码来表示,它应该是这样的:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
解决方案 6:
Windows 中与 Linux 中关键选择相当的“快速”功能是futex,它代表快速用户空间互斥锁。futex 和互斥锁之间的区别在于,使用 futex 时,内核仅在需要仲裁时才参与,因此您无需在每次修改原子计数器时与内核通信。这……可以节省某些应用程序中协商锁的大量时间。
futex 还可以在进程之间共享,使用与共享互斥锁相同的方式。
不幸的是,futexes 的实现非常棘手(PDF)。(2018 年更新,它们并不像 2009 年那么可怕)。
除此之外,它在两个平台上几乎是相同的。您正在以一种(希望)不会导致饥饿的方式对共享结构进行原子、令牌驱动的更新。剩下的只是实现这一点的方法。
解决方案 7:
我只是想补充一点,关键部分被定义为一种结构,对它们的操作是在用户模式上下文中执行的。
ntdll!_RTL_CRITICAL_SECTION
+0x000 调试信息:Ptr32 _RTL_CRITICAL_SECTION_DEBUG
+0x004 锁定计数: Int4B
+0x008 递归计数 : Int4B
+0x00c 所属线程 : Ptr32 Void
+0x010 LockSemaphore : Ptr32 无效
+0x014 旋转次数: Uint4B
而互斥体是内核对象(ExMutantObjectType),在 Windows 对象目录中创建。互斥体操作大多在内核模式下实现。例如,创建互斥体时,最终会在内核中调用 nt!NtCreateMutant。
解决方案 8:
在 Windows 中,临界区是进程的本地区域。互斥锁可以在进程间共享/访问。基本上,临界区的成本要低得多。无法具体评论 Linux,但在某些系统上,它们只是同一事物的别名。
解决方案 9:
Michael 的回答很棒。我为 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
", totalTimeM, totalTime, totalTimeCS);
我的结果是 217、473 和 19(请注意,我最后两个的时间比率与 Michael 的大致相当,但我的机器至少比他的年轻四年,因此您可以看到 2009 年至 2013 年 XPS-8700 问世期间速度提高的证据)。新的互斥锁类的速度是 Windows 互斥锁的两倍,但仍不到 Windows CRITICAL_SECTION 对象速度的十分之一。请注意,我只测试了非递归互斥锁。CRITICAL_SECTION 对象是递归的(一个线程可以反复进入它们,前提是它离开的次数相同)。
解决方案 10:
我发现关于临界区保护代码段不被多个线程进入的解释非常具有误导性。保护代码毫无意义,因为代码是只读的,不能被多个线程修改。人们通常想要的是保护数据不被多个线程修改,从而导致状态不一致。通常,互斥锁(或临界区,实现相同的目的)应该与某些数据相关联。访问此数据的每个代码段都应获取互斥锁/临界区,并在访问完数据后释放。这可能比仅仅锁定线程以防止其进入函数要细粒度得多。此外,根据我的经验,通过某种同步锁定函数更容易出错,尤其是死锁。可以在此处找到一篇涵盖该主题的好文章:
https: //www.bogotobogo.com/cplusplus/multithreaded4_cplusplus11B.php
因此,总而言之(递归)互斥锁和临界区基本上实现相同的目的,即不是保护代码,而是保护数据。
临界区可能比普通内核互斥锁实现得更有效。第一个答案中给出的示例有点误导,因为它没有描述同步原语的设计目的:同步来自多个线程的对某事物的访问。该示例仅测量了临界区/互斥锁从未被其他线程拥有的简单情况。虽然如果两个线程在短时间内互锁访问数据,临界区可能会更有效,但如果我们让许多线程访问同一块数据,临界区可能会效率降低。每个线程都会自旋锁定,直到放弃并等待信号量,这是临界区实现的一部分。在测量执行时间时也应考虑这种情况。
解决方案 11:
如果 AC 函数仅使用其实际参数,则该函数被称为可重入的。
可重入函数可以被多个线程同时调用。
可重入函数示例:
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()
不是可重入的,不能同时被两个或更多线程使用。
某些平台 SDK 附带了可重入版本,strtok()
称为strtok_r()
;
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件