【问题标题】:How to fix slow performance of macOS pthread mutex locks?如何解决 macOS pthread 互斥锁性能缓慢的问题?
【发布时间】:2019-07-11 16:37:34
【问题描述】:

我有一个函数可以修改我的多线程程序中的共享资源。这个函数是线程接触共享资源的唯一地方,它只用于每个线程整体工作的一小部分。

static int64_t
AddToSharedResource(volatile int64_t* value, int64_t to_add)
{
    int64_t result = *value;
    *value += to_add;
    return result;
}

我想让我的应用程序线程安全,所以我在指令之间添加了一个简单的互斥锁。

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

static int64_t
AddToSharedResource(volatile int64_t* value, int64_t to_add)
{
    pthread_mutex_lock(&lock);
    int64_t result = *value;
    *value += to_add;
    pthread_mutex_unlock(&lock);
    return result;
}

这样做会使我的程序慢 10 倍以上,甚至比单线程版本还要慢!

再读一读,似乎是因为 macOS 实现,它使用"fair" mutexes 而不是使用spinlocks,并且实现之间存在一定的权衡,但这种情况是其中一种情况表现不佳。但是,我这样写代码的原因是我已经在 Win32 中编写了程序(锁几乎不会造成任何性能损失),并且我也计划将该功能移植到 Linux。

有没有办法让这个函数在 macOS 中线程安全而不造成巨大的瓶颈,还是我需要重新设计平台层?

【问题讨论】:

  • 您的资源在内存中的放置方式非常重要。我设法通过避免false sharing 来加速线程程序x10 - 并且没有进行任何其他更改。 alignasstd::hardware_destructive_interference_size 在这种情况下是你的朋友。
  • AddToSharedResource(volatile int64_t* value, int64_t to_add) - 为什么指针是volatile?请注意,volatile 表示线程安全。
  • 不,如果您正确同步对资源的访问,则永远不需要volatile。易失性用于改变编译器控制之外的东西,例如直接映射到某些硬件信号的内存地址。
  • 这不是volatile 的用途。它主要用于从硬件寄存器等读取。您将在此处获得的主要效果是防止编译器优化对指针的访问。你只是让你的代码运行得更慢。
  • @TedLyngmo 啊,我明白了!可以试试看!看看编译器能想出什么很有趣:D

标签: c++ multithreading macos pthreads locking


【解决方案1】:

您的示例与std::atomic::fetch_add 完全匹配。 原子操作应该比执行 lock-modify-unlock dance 便宜得多,并且具有允许指定精确 memory ordering semantics 的额外好处。

【讨论】:

  • 感谢您的建议!不过,我必须先更仔细地看一下。您碰巧知道 macOS 的基本说明吗?我对 C++ 线程库不太熟悉;我通常直接调用系统调用。我已经看到clang有blocks extension...可以用它来实现吗?
  • :-) 我猜那是针对我的? pthreads<thread> 的基础。在 Mac 和 Linux 上,它几乎是一个精简的包装器 - 但具有适当的资源管理。
  • blocks 看起来更像 std::async,而且两者都比 std::atomic 高得多
  • @TedLyngmo 我其实很想知道std::atomic::fetch_add!由于 macOS 使用 pthreads,因此应该可以使用系统调用而不是使用 C++ thread/atomic 库来执行此操作。只是好奇 macOS 上的底层调用是什么。
  • 原子操作完全映射到您架构的原子 CPU 指令。
【解决方案2】:

似乎std::atomic::fetch_add(正如@Botje 建议的那样)是在我的架构上使用lock 指令编译的,而在互斥锁/解锁中包围关键部分需要对内核进行两次实际调用(这似乎解释性能差异)。

我对如何使用 MacOS 的 API 而不是 C++ 的标准库(只是为了好玩)生成相同的指令感兴趣。在扫描了他们的文档后,我找到了标题libkern/OSAtomic.h,它定义了原子操作的函数。

#include <libkern/OSAtomic.h>

static int64_t
AddToSharedResource(volatile int64_t* value, int64_t to_add)
{
    int64_t result = OSAtomicAdd64(to_add, value);
    return result - to_add;  // As we want the previous value.
}

但是,这会产生一个弃用警告,建议改用 C++ 的标准库。

'OSAtomicAdd64' is deprecated: deprecated in macOS 10.12 - Use 
    std::atomic_fetch_add_explicit(std::memory_order_relaxed) 
    from <atomic> instead.

所以看起来甚至 MacOS 都希望我们使用 C++ 标准库。另一方面,Windows 并没有弃用它们对应的InterlockedAdd64

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-20
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-24
    相关资源
    最近更新 更多