2

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

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 实现,它使用“公平”互斥锁而不是使用自旋锁,并且实现之间存在一定的权衡,但这种情况是表现不佳的情况之一。但是,我以这种方式编写代码的原因是我已经在 Win32 中编写了程序(锁定几乎不会造成任何性能损失),并且我也计划将该功能移植到 Linux。

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

4

2 回答 2

3

您的示例与std::atomic::fetch_add. 原子操作应该比锁定-修改-解锁舞蹈便宜得多,并且具有允许指定精确的内存排序语义的额外好处。

于 2019-07-11T16:53:06.647 回答
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.

于 2019-07-20T17:54:37.540 回答