编程范式巡礼第二季 并发那些事

编程范式巡礼第二季 并发那些事

继续上周的编程范式话题,今天想聊一下并发范式。

并发也算一种范式?

真正的并发式编程,绝不只是调用线程API或使用synchronized、lock之类的关键字那么简单。从宏观的架构设计,到微观的数据结构、流程控制乃至算法,相比通常的串行式编程都可能发生变化。毫不夸张的说,是又一场思想和技术上革命。

在日常开发中,并发编程难度是比较高的,属于高级程序员才能掌握的内容。其难点在哪里,我们日常习惯的是线性思维,这与并发编程的多维世界观是不同的,提升思考的维度无疑是艰难。但还好,在大神们的努力下,已逐渐化繁为简,这也是并发范式带来的力量。在并发领域有许多的模型,让我们来巡礼一下。

模型1:线程与锁

并发编程以资源共享和竞争为主线。这意味着程序设计将围绕进程的划分与调度、进程之间的通信与同步等来展开。合理的并发式设计需要诸多方面的权衡考量。

线程是对底层硬件过程的形式化,是并发编程的核心。不同的线程各自独立运行,有如一个个的平行宇宙。但是,并发编程并不仅仅串行化编程的叠加,主要的差异在于,线程之间存在共享和竞争。共享资源会带来哪些问题呢?

  • 问题1:脏读

当线程1对共享数据进行修改时,线程2有可能会读到处于中间状态的数据,这个问题称之为脏读。

并发1
并发1

解决的思路比较简单,就是让线程1仅提交最终修改结果,在修改过程中产生并使用快照数据。这种类似影分身的技术称之为MVCC(Multi-Version Concurrency Control)。

并发2
并发2
  • 问题2:丢失更新

当线程1对数据进行修改时,如果线程2同时修改,由于采用了MVCC,双方各自无法看到,那最终提交时,很可能会造成其中一个线程结果与预期不一致,这个问题称为丢失更新。
其解决方法是加锁,在修改前进行加锁,一旦占用,则第二个线程无法获取。

并发3
并发3

脏读和丢失更新是需要同时考虑的,所以标准的多线程处理是同时使用到了MVCC和锁这两个技术。

并发4
并发4
  • 问题3:幻读

在已解决了丢失更新和脏读的情况下,下面要考虑多次读取的情况。如下图所示,线程1对数据集进行了多次读取,但是部分数据在线程2中进行了更新,这时候出现了线程1在没有任何作为的情况下,两次读取不一致的情况!!!这个问题称为幻读。

并发5
并发5

解决幻读的方法是扩大数据的锁范围,不仅仅是更改过的记录,所有读取的记录都要加锁。

并发6
并发6
  • 综述

这就是目前我们最主流的并发与锁的实现思路方法,有非常广泛的使用。不知道大家读完这段的感觉怎么,我看的时候,第一个感觉是复杂,真的非常的烧脑,由于大量概念的堆积对于初学者来说非常不友好;第二个感觉是矛盾,按照最终幻读的解决方案,实际上就是放弃了程序间的并行,绕了一圈,又回到了原点。正因为如此,目前主流的数据库,实际上默认都是放弃对于幻读问题解决的,这也是开发上的一大坑。

综合的来看,这种解决方式学习成本很高,而且还没能解决全部的问题,并不能让人满意。有没有更好点的方法,让我们继续。

模型2:函数式编程与Lambda架构

传统并发模型中,最令人纠结的无疑就是共享数据访问这块了。若不爽,就另辟蹊径。我们能不能不对共享数据进行写入呢?有什么样的程序是只读不写的呢,大神们已经找到了答案,就是上周介绍的函数式编程。

首先想说明的事,纯函数式的编程功能上并不完备,有非常多的缺陷,但其有一个天然的适用场景,就是数学运算分析,也就是我们现在时常挂在嘴边的大数据计算。
由于抛弃掉了共享状态,其代码的健壮性和扩展性得到了大大的增强,只要有足够的计算资源就可以处理无限大的数据。

函数式编程思维比较数学化,难度是比较高的,在此基础上,诞生了Lambda框架,是对应用模式的固化,有助于降低学习成本和大范围推广。Lambda框架既使用了可以进行大规模批处理的MapReduce技术,也使用了可以快速处理数据并及时反馈的流处理技术,这样的混搭能够为大数据问题提供扩展性、响应性和容错性都能优秀的解决方案。

Lambda架构也可以这样来描述:在该架构中,被读取的数据是不可变的,在并行处理过程中数据会依次进入批处理系统(batch system)与流处理系统。从逻辑上看,传输过程发生了两次,一次是在批处理中,一次是在流处理中。在查询时,当这两者都返回结果后,才算是完成一次完整的查询。

20140510204559203

模型3:Actor

函数式编程模型的应用使得并发编程的应用踏入了工业级,带动了大数据的热潮。但是其解决思路是抛弃了可变状态,服务是有损的。对于必须提供无损服务的场景该如何进行改进呢。

从最一开始线程与锁的模型中,我们可以看到串行化是最重的解决方案,但是为了串行化,我们需要MVCC、锁等一系列的工具,比较复杂,Actor模型就是用来简化此类操作的。

Actor模型中抽象出了两个概念Actor和Mailbox,Actor就是指代共享数据,Mailbox管理数据的操作。对于每个Actor的操作,要通过mailbox来进行,在mailbox端实现了队列的控制,从而实现了序列化的效果。

AkkaComponentMatching

Actor模型会带来一些额外的好处:

  1. 用Actor来定义共享数据,边界非常清晰,实现了与主线代码的解耦,最大化减少了序列化的影响,可以有效提升性能。
  2. Actor中引入了消息的概念,是位置透明的,天然支持了分布式的部署。
  3. 在概念清晰之后,代码得到了简化,下面摘录一段Actor的代码,可以看到是封装了并发相关的技术细节,非常的简洁。
class Pong extends Actor {
  def act() {
    var pongCount = 0
    while (true) {
      receive {
        case Ping =>
          if (pongCount % 1000 == 0)
            Console.println("Pong: ping " + pongCount)
          sender ! Pong
          pongCount = pongCount + 1
        case Stop =>
          Console.println("Pong: stop")
          exit()
      }
    }
  }
}

模型4:原子变量

很多情况下我们需要一个高效的、线程安全的并发解决方案。高效意味着耗用资源要少,程序处理速度要快;线程安全也非常重要,这个在多线程下能保证数据的正确性。有一个解决方案是原子变量。

通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。

下面是示例代码:

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。

原子变量在一些对性能有极端要求的系统中(比如Jetty、Tomcat)有非常广泛的应用,是一种精益求精的体现,其在可靠性和性能方面表现很突出,但在易用性方面比较偏计算机思维,理解难度较大,并不够简洁,需要反复练习才能掌握。

小结

在今天的篇文章中,列举了并发范式的四个主流模型:线程与锁、函数式编程、Actor、原子变量。可以看到,每个模型都是在功能、性能和易用之间寻求了一种平衡,并没有一种模型在功能、性能和易用三方面同时达到最优,也就是说没有银弹。
这是我们面对并发问题时的困境,也是挑战,也正说明了并发并不是一个简单的线性问题,我们需要针对具体场景、具体问题进行分析,寻找最适合的解决方法,这也是开发人员需要养成的一种重要素养。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容