除了 InterlockedXxx 函数的简单案例之外,所有这些函数的流行模式似乎是它们实现了自己的锁。
这里的答案似乎都没有真正触及“无锁” CAS循环与互斥锁或自旋锁 之间区别的核心。
重要的区别在于无锁算法可以保证自行取得进展——无需其他线程的帮助。使用锁或自旋锁,任何无法获得锁的可怜线程都完全受拥有锁的线程的支配。无法获取锁的可怜线程除了等待(通过繁忙的等待或操作系统辅助的睡眠)之外什么都不做。
使用在 CAS 上循环的无锁算法,无论其他竞争线程在做什么,都可以保证每个线程都能取得进展。从本质上讲,每个线程都在控制自己的命运。是的,它仍然可能需要循环很多次,但是它循环的次数受到竞争线程数的限制。在大多数情况下,它不能无限循环。(在实践中,可能会发生活锁,例如,由于错误共享而不断失败的LL/SC循环) - 但是线程本身可以再次采取措施来处理这个问题 - 它不会受到摆布另一个持有锁的线程。
至于性能,就看情况了。我见过无锁算法的明显例子,即使在高线程争用情况下,它们的锁定算法也完全胜过它们。std::queue
在一台运行 Debian 7 的 x86-64 机器上,我比较了 C++ Boost.Lockfree队列(基于 Michael/Scott 算法)和一个普通的老式std::mutex
. 在高线程争用下,无锁版本的速度几乎是原来的两倍。
那为什么呢?好吧,无锁算法的性能最终归结为实现细节。算法如何避免ABA?它如何实现安全的内存回收?有很多变体...标记指针、基于 epoch 的回收、RCU/静止状态、危险指针、一般进程范围的垃圾收集等。所有这些策略都对性能有影响,有些还限制了您的应用程序的一般方式可以设计。一般来说,根据我的经验,引用计数方法(或标记指针方法)往往表现不佳。但是替代方案的实现可能要复杂得多,并且需要更多基于线程本地存储或通用垃圾收集的内存回收基础设施。