【问题标题】:On Windows, what cases are a mutex better than a CS within a process?在 Windows 上,哪些情况下互斥体比进程中的 CS 更好?
【发布时间】:2021-06-06 01:46:44
【问题描述】:

在 Windows 上。我认为对于一个进程中的线程之间的同步,临界区对象比互斥锁更好。这是因为 CS 对象工作在用户模式,而互斥对象工作在内核模式。所以 CS 对象比互斥对象轻。

但我见过一个进程使用互斥体而不是 CS 的情况。互斥锁甚至没有使用超时。

所以,我真的很好奇,在什么情况下互斥体比进程中的 CS 更好?

【问题讨论】:

  • CS 是特定于操作系统的——所以如果你想编写 portable 代码,你不应该使用它。
  • @Aconcagua 如果你确实使用它,你应该以一种易于在其他平台上用其他东西替换它的方式使用它
  • 您应该检查是否在 MS-Windows 上 std::mutex 是使用临界区实现的。 std::mutex 不需要使用重量级的 MS-Windows 跨进程互斥锁来实现,因为标准 C++ 没有跨进程同步的概念。
  • 删除 C++ 标签。
  • CS 因其重量轻且支持旋转而有用。 Mutex 可用于进程间同步、检测由于意外线程/进程终止或错误导致的死锁、支持 APC 和等待超时。根本无法观察到差异并不罕见,等待时间往往很长。 MSVC 中的 std::mutex 构建在并发运行时之上,它既不使用 CS 也不使用互斥锁。

标签: windows winapi mutex critical-section


【解决方案1】:

在大多数情况下,您是正确的 - 由于您列出的所有原因,Critical Section 对象优于 Win32 Mutex 对象。 C++ 库中的 std::mutex 也是临界区的另一种变体。 pthread_mutex_t 实例也是如此。

我能想到的在同一进程的线程之间使用 Win32 互斥锁而不是更简单的临界区的唯一情况是使用“命名”互斥锁。使用 CRITICAL_SECTIONs,两个线程需要共享一个指向 CRITICAL_SECTION 的指针。

但是假设您有两段不同的代码,它们位于不同的组件和线程中。由于代码目前的架构方式(例如,单独的 DLL 和库),确实没有简单的方法可以在两个代码路径之间共享 CRITICAL_SECTION 指针,而无需在两者之间进行大量管道。

命名互斥体解决了这个问题。该名称是您作为第三个参数传递给CreateMutex. 的字符串,两个组件在初始化时都可以执行以下操作

HANDLE hMutex = CreateMutex(nullptr, FALSE, L"App_Resource_Thing");

现在它们对 SAME 互斥对象具有唯一句柄。然后他们都可以使用自己的句柄进入互斥锁:

DWORD dwWait = WaitForSingleObject(hMutex, INFINITE);
if ((dwWait == 0) || (dwWait == WAIT_ABANDONED))
{
    // operate on shared resource

    // release mutex to whoever wants it next
    ReleaseMutex(hMutex);
}

现在命名互斥体带来了另一个问题 - 安全性。使用上面的示例,另一个应用程序,假设它知道互斥锁的名称,可以出现并永远锁定它,这将有效地冻结另一个应用程序。或者更一般地说,如果使用硬编码名称,您自己的 EXE 运行的另一个实例将与第一个实例发生冲突。使用 SECURITY_DESCRIPTOR 和/或在运行时为互斥锁生成唯一名称,其他流氓进程无法猜到,这将有助于缓解该问题。

【讨论】:

  • 另外我不认为WaitForMultipleObjects 可以与临界区一起使用?例如,您的线程只等待两件事是很常见的:等待互斥体并等待事件信号关闭。
  • 正确。但是 OP 在他引用的示例中已经调用了The mutex didn't even use a timeout.。依靠超时进入关键部分,这在某种程度上也是一种设计缺陷,如果不是一种奇怪的设计模式的话。所以我真的不想通过提及它来鼓励这一点。
【解决方案2】:

我认为问题是关于 Windows Mutex,而不是 std::mutex

  • Mutex 可以将超时与WaitForSignleObject 或其他等待函数一起使用,如果它指定了超时。
  • [Msg]WaitForMultipleObjects[Ex] 可以等待互斥体或另一个对象(另一个互斥体)。使用临界区,您只能在临界区等待,这种等待不能被其他东西打断。
  • 拥有互斥锁的线程退出时,互斥锁可以发出信号,请参阅WAIT_ABANDONED_0
  • 互斥是公平的,关键部分是不公平的。这意味着,如果您经常争用两个或多个线程,则使用临界区的线程可能会不断“获胜”,而使用互斥锁的线程将(大约)遵循 FIFO 顺序。

请注意,前两点可以通过结合使用CONDITION_VARIABLECRITICAL_SECTION 来解决,但需要编写更多代码。


请注意,有一个优于 CRITICAL_SECTION 的同步对象称为 SRWLOCK。它可以保持共享所有权,但您可以使用独占所有权 API。它比临界区更轻量级,因为它不需要维护递归计数和一些遗留兼容性问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-04
    • 1970-01-01
    • 2022-06-30
    • 2012-07-17
    • 2011-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多