Windows中互斥锁、临界区等的成本

9
我在某处读到,mutex的开销并不大,因为只有在竞争情况下才会发生上下文切换。
在Linux中也称作Futexes。
在Windows中是否也适用相同的情况?在Linux中,关键段(Critical Section)是否更适合作为互斥锁?
据我所知,与Mutex相比,关键段提供更优秀的性能表现。这对每种情况都成立吗?
在Windows中,是否存在互斥锁比关键段更快的特殊情况?
假设只有一个进程-线程访问互斥锁(只是为了消除关键段的另一个好处)。
附加信息:操作系统为Windows Server,使用的编程语言为C++。

当然,如果您的应用程序经常出现争用,您可能需要重新考虑互斥锁没有太多开销的想法。 - pattivacek
2个回答

13
考虑到“临界区”和“互斥量”的特定目的,当您需要多个线程触及同一数据时,我认为您不能问及成本问题,显然,如果您只需要递增/递减一个数字,您可以在“易失性”数字上使用“Interlocked*()”函数,然后就可以开始了。但是对于任何更复杂的操作,您需要使用同步对象。
Windows^提供的“同步对象”开始阅读。所有函数都在那里列出,分组清晰并得到了很好的解释。其中一些仅适用于Windows 8。
关于您的问题,“临界区”比“互斥量”花费更少,因为它们设计用于在同一进程中运行。请阅读this^this^或者只需参考以下引用。
关键段对象提供了类似于互斥对象提供的同步功能,除了关键段仅可被单个进程的线程使用。事件、互斥和信号量对象也可以在单进程应用程序中使用,但是关键段对象为互斥同步提供了稍微更快、更有效的机制(处理器特定的测试和设置指令)。像互斥体对象一样,关键段对象一次只能由一个线程拥有,这使其对于保护共享资源免受同时访问非常有用。与互斥对象不同,没有办法判断关键段是否已被丢弃。
我使用关键段进行同一进程的同步,使用互斥体进行跨进程同步。只有当我真正需要知道同步对象是否被丢弃时,我才在同一进程中使用互斥体。所以,如果您需要同步对象,问题不在于成本而在于哪个更便宜:) 实际上没有其他选择,只会存在内存损坏。

PS: 在这里提到的替代方案可能有其他选择,但我总是选择核心平台特定功能而不是跨平台功能。这样会更快!所以如果你使用Windows,请使用Windows的工具 :)

UPDATE

根据您的需求,您可以尝试在线程中尽可能地完成自包含的工作,仅在结束或偶尔时将数据合并。

愚蠢的例子拿一个URL列表。您需要抓取并分析它们。

  1. 加入一堆线程,逐一从输入列表中挑选URL。对于每个处理的URL,您都要将结果集中起来。这是实时且酷炫的。
  2. 或者您可以投入多个线程,每个线程都有一部分输入的URL。这样就不需要同步选择过程了。您可以在线程中存储分析结果,并在最后组合结果。或者每10个URL组合一次结果,而不是每个URL。这将大大减少同步操作。

因此,通过选择正确的工具并思考如何降低锁定和解锁,可以降低成本。但是成本无法消除 :)

PS: 我只想着URLs :)

更新2:

在一个项目中需要进行一些测量,结果相当令人惊讶:

  • std::mutex 是最昂贵的,跨平台需要付出代价。
  • Windows原生 Mutexstd 快两倍。
  • Critical Section原生 Mutex 快两倍。
  • SlimReadWriteLockCritical Section 的 +-10%。
  • 我自制的 InterlockedMutex (自旋锁)Critical Section 快1.25x - 1.75x。

我知道我不能消除成本,但只是在思考关键段或互斥是否适合我。不过还是谢谢你提供的信息。 - Desert Ice
3
不应单独依靠volatile来进行线程编程,而应该使用atomic或将volatile与内存栅栏相结合。否则,在Linux / gcc等平台上,您的volatile访问可能会被重新排序。因此,对于易变的情况,应该避免过度依赖volatile。 - Петър Петров
1
并不是说没有其他选择。Windows 实现是系统级的,受权限和访问列表所控制,互斥体旨在成为进程间同步接口,它们不等同于经典的互斥量。关键部分接口与线程优先级管理器相关,Windows 中的线程拥有自己的内存区域 - 线程本地存储。对这些函数的调用涉及相当数量的 DLL 调用。如果不需要与操作系统进行深入而广泛的交互,为了性能考虑,可以使用非 API、用户空间的互斥体、自旋锁、原子操作等。 - Swift - Friday Pie

1

我可以帮忙翻译该内容。该段文字讲述了在Windows 8上,使用std::mutex进行编程时,在非争用情况下,通过使用自己定制的自旋锁,通常可以获得3-4倍的速度提升。

基于互斥量的

auto time = TimeIt([&]() {
for (int i = 0; i < tries; i++) {
    bool val = mutex.try_lock();
    if (val) {
        data.value = 1;
    }
}

});

自制无锁编程

time = TimeIt([&]() {
    for (int i = 0; i < tries; i++) {
        if (!guard.exchange(true)) {
            // I own you
            data.value = 1;
            guard.store(true);
        }
    }
});

测试是在x86上进行的。

我还没有弄清楚std::mutex在Windows上使用下划线的原因,因为它会生成很多代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接