Multithreading 什么时候无锁数据结构的性能低于互斥(互斥)?

Multithreading 什么时候无锁数据结构的性能低于互斥(互斥)?,multithreading,scheduling,mutex,atomic,lock-free,Multithreading,Scheduling,Mutex,Atomic,Lock Free,我在某个地方读到(再也找不到页面)无锁数据结构“对于某些工作负载”更有效,这似乎意味着有时它们实际上更慢,或者在某些情况下从中获得的收益可能为零。在我看来,执行原子操作的锁指令的大约100个周期的命中比睡觉和等待调度程序唤醒进程要快得多,因此我不清楚在什么情况下,无锁数据结构不如老式的互斥体更好。如果锁在99%的时间内可用,并且进程不必进入睡眠状态,那么互斥锁是否会更快?假设有一个合适的无锁数据结构可用,是否有一个好的经验法则来知道该走哪条路?我不知道如何让它变慢,但这肯定会让它更难正确。在许多

我在某个地方读到(再也找不到页面)无锁数据结构“对于某些工作负载”更有效,这似乎意味着有时它们实际上更慢,或者在某些情况下从中获得的收益可能为零。在我看来,执行原子操作的锁指令的大约100个周期的命中比睡觉和等待调度程序唤醒进程要快得多,因此我不清楚在什么情况下,无锁数据结构不如老式的互斥体更好。如果锁在99%的时间内可用,并且进程不必进入睡眠状态,那么互斥锁是否会更快?假设有一个合适的无锁数据结构可用,是否有一个好的经验法则来知道该走哪条路?

我不知道如何让它变慢,但这肯定会让它更难正确。在许多情况下,这两种方法在性能上几乎相同(或者如果只需要500皮秒而不是100皮秒并不重要),那么选择最简单的方法-通常
锁定

很少有情况下,额外的性能是关键;如果是这样,我想您最好使用来自已建立库的预滚模式实现。让无锁代码正常工作(并证明它在所有条件下正常工作)通常非常困难


还请注意,某些环境提供的锁定级别高于操作系统提供的互斥锁;互斥行为,但没有一些开销(例如,
Monitor
in.NET)。

无锁数据结构将以某种方式使用体系结构中的原子语义来执行其核心操作。当您这样做时,您可以使用机器的整个内部排除机制来确保数据的正确排序或隔离。互斥体或临界部分也会执行此操作,但它仅对单个标志执行一次。当互斥锁或关键部分速度较慢时,锁获取失败(存在争用)。在这种情况下,操作系统还调用调度程序来挂起线程,直到释放排除对象


因此,当您的无锁数据结构在每个核心方法中使用多个原子操作时,当屏蔽关键部分的单个锁可以提供相同的语义时,并且在实践中,对于所讨论的数据结构几乎没有争用时,这似乎是合乎逻辑的,那么事实上,使用操作系统提供的锁定机制比尝试构建自己的锁定机制更有意义。

我想在答案的这一部分补充一点: 互斥锁或关键部分速度较慢时,锁获取失败(存在争用)。在这种情况下,操作系统还调用调度程序挂起线程,直到释放排除对象

似乎不同的操作系统在锁获取失败时可以有不同的方法。我使用HP-UX,例如它有一种更复杂的方法来锁定互斥锁。以下是它的描述:

。。。另一方面,改变环境是一个代价高昂的过程。如果等待时间很短,我们最好不要进行上下文切换。为了平衡这些需求,当我们试图获取一个信号量并发现它被锁定时,我们要做的第一件事就是短时间的旋转等待。调用例程psema_spin_1()来旋转多达50000个时钟周期,以获取锁。如果我们在50000个周期后无法获得锁,那么调用psema_switch_1()放弃处理器,让另一个进程接管


请记住,互斥可以很好地实现为无锁数据结构,因为它使用一个或几个原子对象来表示其状态。这是一种错误的二分法

更好的是考虑是否需要允许多个线程等待对某些操作的访问或阻止直到发出信号。每个线程都需要一个等待线程队列。前者对等待访问同步区域的线程进行排队,而后者对等待信号的线程进行排队。Java类和提供了这样一个队列,尤其是CLH队列,人们可以在其上构建互斥体、条件和其他基于队列的原语


如果您的需求只支持一个线程执行一组独占的工作,而其他线程可以自由地执行其他工作,而不是等待它们自己也可以执行相同的工作,那么使用无锁技术是可能的。这样做是否会使运行时间更快取决于基准测试,取决于线程争夺这些同步控制的频率和数量,以及线程是否需要独立执行其他工作。

实现无锁数据结构的常见方法是对不可变对象进行可变引用,并让任何想要更改结构的内容获取引用,生成应用了适当更改的对象的新版本,然后比较交换引用以指向新对象。如果比较交换有效,那就太好了。如果没有,请丢弃新对象,重新抓取引用,然后重新开始

如果生成新对象的成本很低,并且争用级别足够低,CompareExchange通常可以正常工作,那么这种方法可以很好地工作。如果存在大量争用,并且生成新对象的速度很慢,则N个线程同时尝试更新可能需要N^2时间才能完成。作为一个极端的例子,假设100个线程在一个CPU上运行,一次更新需要100毫秒的CPU时间(刚好超过一个时间片),并且所有100个线程都试图一次更新一个对象。在前十秒内,每个线程将基于原始对象生成一个新对象。其中一个线程将成功执行CompareExchange,而其他线程将全部失败。这个