【发布时间】:2010-10-22 11:19:21
【问题描述】:
请从Linux、Windows的角度解释一下?
我正在用 C# 编程,这两个术语会有所不同吗?请尽可能多地发布,并附上示例等......
谢谢
【问题讨论】:
标签: windows linux multithreading programming-languages
请从Linux、Windows的角度解释一下?
我正在用 C# 编程,这两个术语会有所不同吗?请尽可能多地发布,并附上示例等......
谢谢
【问题讨论】:
标签: windows linux multithreading programming-languages
如果 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();
【讨论】:
Linux 中与关键选择相同的“快速”Windows 将是 futex,它代表快速用户空间互斥锁。 futex 和 mutex 之间的区别在于,使用 futex,内核仅在需要仲裁时才涉及,因此您可以节省每次修改原子计数器时与内核对话的开销。这 .. 可以节省 大量 在某些应用程序中协商锁定的时间。
futex 也可以在进程之间共享,使用共享互斥锁的方式。
不幸的是,futex 可以是very tricky to implement (PDF)。 (2018 年更新,它们并不像 2009 年那样可怕)。
除此之外,它在两个平台上几乎相同。您正在以一种(希望)不会导致饥饿的方式对共享结构进行原子的、令牌驱动的更新。剩下的只是实现它的方法。
【讨论】:
互斥锁是一个线程可以获取的对象,防止其他线程获取它。这是建议性的,不是强制性的;线程可以使用互斥锁所代表的资源而不需要获取它。
临界区是操作系统保证不会被中断的一段代码。在伪代码中,它会是:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
【讨论】:
迈克尔的回答很好。我为 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);
我的结果是 217、473 和 19(请注意,我最后两次的时间比率与迈克尔的大致相当,但我的机器比他的机器至少年轻 4 岁,所以你可以看到速度增加的证据2009 年和 2013 年,当 XPS-8700 出现时)。新的互斥锁类的速度是 Windows 互斥锁的两倍,但仍不到 Windows CRITICAL_SECTION 对象速度的十分之一。请注意,我只测试了非递归互斥锁。 CRITICAL_SECTION 对象是递归的(一个线程可以重复进入它们,只要它离开的次数相同)。
【讨论】:
从理论上讲,critical section 是一段不能由多个线程同时运行的代码,因为该代码访问共享资源。
mutex 是一种算法(有时是数据结构的名称),用于保护临界区。
Semaphores 和 Monitors 是互斥体的常见实现。
在实践中,Windows 中提供了许多互斥锁实现。它们的主要区别在于它们的锁定级别、范围、成本以及在不同争用级别下的性能。请参阅CLR Inside Out - Using concurrency for scalability 了解不同互斥体实现的成本图表。
可用的同步原语。
lock(object) 语句是使用 Monitor 实现的 - 请参阅 MSDN 以获取参考。
在过去几年中,对non-blocking synchronization 进行了大量研究。目标是以无锁或无等待的方式实现算法。在这样的算法中,一个进程帮助其他进程完成它们的工作,以便该进程最终可以完成它的工作。因此,即使尝试执行某些工作的其他进程挂起,进程也可以完成其工作。使用锁,他们不会释放他们的锁并阻止其他进程继续。
【讨论】:
为了增加我的 2 美分,关键部分被定义为一个结构,并且对它们的操作在用户模式上下文中执行。
ntdll!_RTL_CRITICAL_SECTION +0x000 调试信息:Ptr32 _RTL_CRITICAL_SECTION_DEBUG +0x004 LockCount : Int4B +0x008 递归计数:Int4B +0x00c OwningThread : Ptr32 无效 +0x010 LockSemaphore : Ptr32 无效 +0x014 旋转计数:Uint4B
而互斥体是在 Windows 对象目录中创建的内核对象 (ExMutantObjectType)。互斥操作主要在内核模式下实现。例如,在创建 Mutex 时,您最终会在内核中调用 nt!NtCreateMutant。
【讨论】:
对于 Windows,临界区比互斥体轻。
互斥锁可以在进程之间共享,但总是会导致对内核的系统调用有一些开销。
关键部分只能在一个进程中使用,但其优点是它们只在争用情况下切换到内核模式 - 非竞争获取,这应该是常见的情况,速度非常快。在争用的情况下,它们进入内核等待某个同步原语(如事件或信号量)。
我编写了一个快速示例应用程序,用于比较两者之间的时间。在我的系统上进行 1,000,000 次无竞争的获取和释放,互斥量需要一秒钟。 1,000,000 次获取的关键部分大约需要 50 毫秒。
这是测试代码,如果 mutex 是第一个或第二个,我运行它并得到类似的结果,所以我们没有看到任何其他效果。
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 上的关键部分:
InterlockedCompareExchange 操作一样简单在 linux 中,我认为它们有一个“自旋锁”,其作用类似于具有自旋计数的临界区。
【讨论】:
Critical Section 和 Mutex 不是操作系统特定的,它们是多线程/多处理的概念。
关键部分 是一段只能在任何给定时间由它自己运行的代码(例如,有 5 个线程同时运行,一个名为“critical_section_function”的函数会更新一个数组......你不希望所有 5 个线程都更新数组一次。所以当程序运行critical_section_function()时,没有其他线程必须运行它们的critical_section_function。
互斥体* 互斥锁是一种实现临界区代码的方式(把它想象成一个令牌......线程必须拥有它才能运行临界区代码)
【讨论】:
在 Windows 中,关键部分是您的进程的本地部分。可以跨进程共享/访问互斥锁。基本上,关键部分要便宜得多。无法具体评论 Linux,但在某些系统上它们只是同一事物的别名。
【讨论】: