无锁并不像人们所说的那么好

原文链接译文连接,译者:梁海舰,校对:李任

注意,我将在这篇文章中说一些关于“无锁”和“无等待”算法的坏话。在我们开始之前,让我来问你一个问题:

你之前有过需要无锁算法来解决问题吗?为什么必须使用无锁算法来解决?

这是一个意味深长的问题,在某种意义上,我们尝试区分哪些问题需要“无锁”或者受益于“无锁”提供的保证,哪些问题使用阻塞解决方案就已经足够了。那么,什么样的问题可以利用无锁算法解决呢?

你会说高竞争下的扩展性?是的,听起来无锁算法确实是合适的候选方案,但是,实际上它很大程度取决于算法的某一些特征。

例如:如果循环中有很多CAS操作,当你增加线程数量时候,那些CAS操作将增加大量的重试,记住CAS操作的代价是很高的,并且很有可能成为该算法的瓶颈。实际上,这意味着CAS操作上越多的线程竞争,你的代码运行的整体速度越慢,不管你添加多少线程。

如果你一开始没有很多线程,那为什么不使用阻塞的方式呢?在这个例子中使用阻塞的方式通常更简单、更快实现。

如果问题允许,你甚至可以使用读写锁,不同于大部分无锁算法,读写锁已经展示了读线程的数量可扩展。

还有人说用在解决高竞争下的低延迟,好吧,无锁仅仅保证了某些线程能够执行,但让我们假设你没有一个高优先级的线程或一个高于其他线程优先级的线程,未必当前线程就在执行它的任务(在这种情况下你需要无等待算法实现低延迟保证)。

对于这样的情况,无锁确实给了你比阻塞更好的保证,但是除此之外不要期望得太多。首先,你很可能丢失一些性能(但不是很多),其次,如果其他线程持续的执行,那么特定的任务可能耗费无限的时间直至完成并且导致当前操作重启。这意味着那些循环中包含很多CAS操作的无锁算法,执行起来慢得离谱,其原因在于重试的次数,并不是所谓的复杂性。

现在我们知道为什么使用无锁,值得注意的是,许多无锁算法在它们执行的一些步骤中会暗自分配和释放内存。

我所知道的,没有一个系统使用“无锁”算法进行内存管理,事实上,在大部分系统中,内存分配有时候执行得非常慢。想象一下你申请一个对象,系统需要为你获得一个新的内存页,更糟糕的是,内存不足,需要将它旧的内容从内存中交换到硬盘中。

如果你关注的是扩展能力,那么偶尔的阻塞,内存分配慢并不是太坏,但问题是你需要的是低延迟,使用无锁进行内存分配、释放没有使用到任何“无锁”算法自身提供的保证。不管使用有内存管理或无内存管理的系统都有这个棘手的问题,换而言之有没有使用内存回收也是一样。

我认为无锁算法的适用性影响非常大,大到我们需要有两个清楚的分类来为无锁算法命名:

无界无锁:这个算法是无锁的,但是它的方法中或多或少需要为新的对象分配内存或者释放一些对象占用的内存。

有界无锁:这个算法是无锁定,并且它的方法中没有涉及到对象分配内存或者是有限制分配并且一开始预先分配,然后使用一个池设计模式,池也是采用无锁方式实现的。

对于“无界无锁”,如果我们教条主义,我们甚至可以说“无界无锁”算法完全不是无锁,因为算法中会有操作被阻塞,并且会导致任务阻塞。

我不会那么教条主义,毕竟内存分配通常是很快的,并且垃圾回收器只是偶尔执行。

有一件事是可以肯定的,那就是给定一个需要解决低延迟的问题,它有两个解决方案可选,一个是“无界无锁”,另一个是“有界无锁”,我会选择有界的那个,即便它性能更低。

顺便说一下,把上面所有段落中的“无锁”字眼替换成“无等待”,它仍然适用。

话虽如此,我想再重申一下我偏爱的并发算法的顺序,下面的算法中最上面的是你可以实现的最好的算法:

  1. 有界集居数无关无等待
  2. 有界无等待
  3. 有界无锁
  4. 无界集居数无关无等待
  5. 无界无等待
  6. 无界无锁
  7. 阻塞
  8. 文章转自 并发编程网-ifeve.com
时间: 2024-11-08 20:26:50

无锁并不像人们所说的那么好的相关文章

《OpenACC并行程序设计:性能优化实践指南》一 1.5 无锁编程

1.5 无锁编程 互斥锁是用于同步进程或线程的常用机制,这些进程或线程需要访问并行程序中的一些共享资源.互斥锁就像它们名字所说的:如果一个线程锁住了资源,另一个线程希望访问它需要等待第一个线程解锁这个资源.一旦资源被解锁,第二个线程在处理这个资源时会一直锁住它.程序的线程必须遵守:一旦使用完共享资源尽快解锁,以保持程序执行流程. 由于OpenACC中没有锁,编程人员需要熟悉无锁编程和数据结构的概念.无锁方法保证至少一个执行该方法的线程的进展.可能存在某些线程可以被延迟的情况,但是保证至少一个线程

无锁并发框架Disruptor

概述 在逛并发编程网的时候,看到了并发框架Disruptor译文这个系列文章. Martin Fowler在自己网站上写了一篇LMAX架构(译文)的文章,在文章中他介绍了LMAX是一种新型零售金融交易平台,它能够以很低的延迟产生大量交易.这个系统是建立在JVM平台上,其核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单.业务逻辑处理器完全是运行在内存中,使用事件源驱动方式.业务逻辑处理器的核心是Disruptor. Disruptor它是一个开源的并发框架,能够在无锁的情况下实现网络

C#算法之基于无锁的C#并发队列实现

最近开始学习无锁编程,和传统的基于Lock的算法相比,无锁编程具有其独特的优点,Angel Lucifer 的关于无锁编程一文对此有详细的描述. 无锁编程的目标是在不使用Lock的前提下保证并发过程中共享数据的一致性,其主要的实现基础是CAS 操作,也就是compare_and_swap,通过处理器提供的指令,可以原子地更新共享数据,并同时监测其他线 程的干扰,.Net中的对应实现是InterLocked.CompareExchange函数. 既然不使用Lock,那在无锁编程中要时刻注意的是,代

ReferenceCountSet无锁实现

记得很久以前有一次面试被问到如何编写无锁程序,我当时觉得那个面试官脑子进水了,我们确实可以在某些情况下减少锁的使用,但是怎么可能避免呢?当然我现在还是持这种观点,在Java中,你可以有很多方法减少锁的使用(至少在你自己的代码中看起来): 1.     比如常见的可以使用volatile关键字来保证某个字段在一个线程中的更新对其他线程的可见性: 2.     可以使用concurrent.atomic包中的各种Atomic类来实现某些基本类型操作的,它主要采用忙等机制(CAS,compare an

无锁和无等待的定义和例子

原文链接,译文连接,译者:周可人,校对:梁海舰 在查阅google之后,我发现没有一处对并发算法或是数据结构规定的演进条件(progress condition,注:参考[1],译者认为翻译为演进状态更为合适)做合理的解释.甚至在"The Art of Multiprocessor Programming"中也只有围绕书本的一小段定义,大部分定义是单行的句子,因而造成了我们普通人含义模糊的理解,所以在这里我把我对这些概念的理解整理在一起,并且在每一种概念后面给出相应的例子. 我们先将演

线程-一个完全无锁无原子的疑问,以及猜想?

问题描述 一个完全无锁无原子的疑问,以及猜想? 先基于一个单生产单消费的情况,我写了如下一个class:templateclass SingleLockFree{public:SingleLockFree(){m_tail = new Node();m_Head = m_tail;}~SingleLockFree(){ //做最后未处理的内存的释放}void Push(T t)//生产线程{Node* p = new Node();//内存分配待优化m_tail->_data = t;m_tai

无锁并发和无等待并发的对比分析

原文地址:作者:rethinkdb  译者:sooerr 校对:方腾飞 有两种非阻塞线程同步算法,即无锁和无等待,这两种算法经常会产生混淆. 在无锁系统中,当任何特定的运算被阻塞的时候,所有CPU可以继续处理其他的运算.换种方式说,在无锁系统中,当给定线程被其他线程阻塞的时候,所有CPU可以不停的继续处理其他工作.无锁算法大大增加系统整体的吞吐量,因为它只偶尔会增加一定的交易延迟.大部分高端数据库系统是基于无锁算法而构造的,以满足不同级别. 相反,无等待算法保证了所有CPU在连续处理有效工作的时

从volatile解读ConcurrentHashMap(jdk1.6.0)无锁读

作者:绫萱 volatile常常用于修饰多线程共享变量,用来保证该变量的可见性.volatile的语意:某个写线程对volatile变量的写入马上可以被后续的某个读线程"看"到. volatile保证可见性的原理:volatile是通过在编译器生成字节码时,在对volatile变量进行读写指令序列的前后加入内存屏障,来禁止一些处理器重排序保证写入一定发生在读之前的这种happen-before关系.   简单理解:在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变

使用JAVA实现高并发无锁数据库操作步骤分享_java

1. 并发中如何无锁.一个很简单的思路,把并发转化成为单线程.Java的Disruptor就是一个很好的例子.如果用java的concurrentCollection类去做,原理就是启动一个线程,跑一个Queue,并发的时候,任务压入Queue,线程轮训读取这个Queue,然后一个个顺序执行. 在这个设计模式下,任何并发都会变成了单线程操作,而且速度非常快.现在的node.js, 或者比较普通的ARPG服务端都是这个设计,"大循环"架构.这样,我们原来的系统就有了2个环境:并发环境 +