小课堂week17 编程范式巡礼第二季 并发那些事
编程范式巡礼第二季 并发那些事
继续上周的编程范式话题,今天想聊一下并发范式。
并发也算一种范式?
真正的并发式编程,绝不只是调用线程API或使用synchronized、lock之类的关键字那么简单。从宏观的架构设计,到微观的数据结构、流程控制乃至算法,相比通常的串行式编程都可能发生变化。毫不夸张的说,是又一场思想和技术上革命。
在日常开发中,并发编程难度是比较高的,属于高级程序员才能掌握的内容。其难点在哪里,我们日常习惯的是线性思维,这与并发编程的多维世界观是不同的,提升思考的维度无疑是艰难。但还好,在大神们的努力下,已逐渐化繁为简,这也是并发范式带来的力量。在并发领域有许多的模型,让我们来巡礼一下。
模型1:线程与锁
并发编程以资源共享和竞争为主线。这意味着程序设计将围绕进程的划分与调度、进程之间的通信与同步等来展开。合理的并发式设计需要诸多方面的权衡考量。
线程是对底层硬件过程的形式化,是并发编程的核心。不同的线程各自独立运行,有如一个个的平行宇宙。但是,并发编程并不仅仅串行化编程的叠加,主要的差异在于,线程之间存在共享和竞争。共享资源会带来哪些问题呢?
- 问题1:脏读
当线程1对共享数据进行修改时,线程2有可能会读到处于中间状态的数据,这个问题称之为脏读。
解决的思路比较简单,就是让线程1仅提交最终修改结果,在修改过程中产生并使用快照数据。这种类似影分身的技术称之为MVCC(Multi-Version Concurrency Control)。
- 问题2:丢失更新
当线程1对数据进行修改时,如果线程2同时修改,由于采用了MVCC,双方各自无法看到,那最终提交时,很可能会造成其中一个线程结果与预期不一致,这个问题称为丢失更新。
其解决方法是加锁,在修改前进行加锁,一旦占用,则第二个线程无法获取。
脏读和丢失更新是需要同时考虑的,所以标准的多线程处理是同时使用到了MVCC和锁这两个技术。
- 问题3:幻读
在已解决了丢失更新和脏读的情况下,下面要考虑多次读取的情况。如下图所示,线程1对数据集进行了多次读取,但是部分数据在线程2中进行了更新,这时候出现了线程1在没有任何作为的情况下,两次读取不一致的情况!!!这个问题称为幻读。
解决幻读的方法是扩大数据的锁范围,不仅仅是更改过的记录,所有读取的记录都要加锁。
- 综述
这就是目前我们最主流的并发与锁的实现思路方法,有非常广泛的使用。不知道大家读完这段的感觉怎么,我看的时候,第一个感觉是复杂,真的非常的烧脑,由于大量概念的堆积对于初学者来说非常不友好;第二个感觉是矛盾,按照最终幻读的解决方案,实际上就是放弃了程序间的并行,绕了一圈,又回到了原点。正因为如此,目前主流的数据库,实际上默认都是放弃对于幻读问题解决的,这也是开发上的一大坑。
综合的来看,这种解决方式学习成本很高,而且还没能解决全部的问题,并不能让人满意。有没有更好点的方法,让我们继续。
模型2:函数式编程与Lambda架构
传统并发模型中,最令人纠结的无疑就是共享数据访问这块了。若不爽,就另辟蹊径。我们能不能不对共享数据进行写入呢?有什么样的程序是只读不写的呢,大神们已经找到了答案,就是上周介绍的函数式编程。
首先想说明的事,纯函数式的编程功能上并不完备,有非常多的缺陷,但其有一个天然的适用场景,就是数学运算分析,也就是我们现在时常挂在嘴边的大数据计算。
由于抛弃掉了共享状态,其代码的健壮性和扩展性得到了大大的增强,只要有足够的计算资源就可以处理无限大的数据。
函数式编程思维比较数学化,难度是比较高的,在此基础上,诞生了Lambda框架,是对应用模式的固化,有助于降低学习成本和大范围推广。Lambda框架既使用了可以进行大规模批处理的MapReduce技术,也使用了可以快速处理数据并及时反馈的流处理技术,这样的混搭能够为大数据问题提供扩展性、响应性和容错性都能优秀的解决方案。
Lambda架构也可以这样来描述:在该架构中,被读取的数据是不可变的,在并行处理过程中数据会依次进入批处理系统(batch system)与流处理系统。从逻辑上看,传输过程发生了两次,一次是在批处理中,一次是在流处理中。在查询时,当这两者都返回结果后,才算是完成一次完整的查询。
模型3:Actor
函数式编程模型的应用使得并发编程的应用踏入了工业级,带动了大数据的热潮。但是其解决思路是抛弃了可变状态,服务是有损的。对于必须提供无损服务的场景该如何进行改进呢。
从最一开始线程与锁的模型中,我们可以看到串行化是最重的解决方案,但是为了串行化,我们需要MVCC、锁等一系列的工具,比较复杂,Actor模型就是用来简化此类操作的。
Actor模型中抽象出了两个概念Actor和Mailbox,Actor就是指代共享数据,Mailbox管理数据的操作。对于每个Actor的操作,要通过mailbox来进行,在mailbox端实现了队列的控制,从而实现了序列化的效果。
Actor模型会带来一些额外的好处:
- 用Actor来定义共享数据,边界非常清晰,实现了与主线代码的解耦,最大化减少了序列化的影响,可以有效提升性能。
- Actor中引入了消息的概念,是位置透明的,天然支持了分布式的部署。
- 在概念清晰之后,代码得到了简化,下面摘录一段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、原子变量。可以看到,每个模型都是在功能、性能和易用之间寻求了一种平衡,并没有一种模型在功能、性能和易用三方面同时达到最优,也就是说没有银弹。
这是我们面对并发问题时的困境,也是挑战,也正说明了并发并不是一个简单的线性问题,我们需要针对具体场景、具体问题进行分析,寻找最适合的解决方法,这也是开发人员需要养成的一种重要素养。
小课堂week17 编程范式巡礼第二季 并发那些事的更多相关文章
- 小课堂week19 编程范式巡礼最终季 超级范式
编程范式巡礼(最终季)--超级范式 本周是编程范式系列的最后一次分享,让我们拉长视角,看向远方,进入"元编程"的领域,在<冒号课堂>中起了个很酷的名字:"超级 ...
- 小课堂week16 编程范式巡礼第一季 三大基石
编程范式巡礼第一季 三大基石 最近迷上了一些哲史类书籍,回望过去.放眼未来,往往沉浸在其思维之美中无法自拔.计算机编程是一门非常年轻的学科,沉淀不足也是年轻的一个侧面,在编程领域,有足够思想深度的作品 ...
- 小课堂week18 编程范式巡礼第三季 谈谈依赖反转
编程范式巡礼第三季--谈谈依赖反转 今天会进入深一点的主题,谈一个软件开发的"道":依赖反转.根据我的观察,这也是架构师与程序员的分水岭之一. 什么是依赖反转 引出问题 让我们从U ...
- 冒号课堂 编程范式与OOP思想
上篇:编程范式与编程语言 第1课 开班导言 第2课 重要范式 第3课 常用范式 第4课 重温范式 第5课 语言小谈 第6课 语言简评 下篇:抽象机制与对象范式 第7课 抽象封装 第8课 抽象接口 第9 ...
- 2019 计蒜之道 初赛 第二场 B. 百度AI小课堂-上升子序列(简单) ( 实现)
题目背景 91029102 年 99 月 22 日,百度在 X 市 XX 中学举办的第一场 AI 知识小课堂大获好评!同学们对矩阵的掌握非常棒. 今天的 AI 知识小课堂的第二场开讲啦.本场 AI ...
- 2019 计蒜之道 初赛 第二场 A 百度AI小课堂-矩阵问题 ( 等差数列求和公式)
题目背景 91029102 年 99 月 11 日,百度在 X 市 XX 中学举办了一场 AI 知识小课堂,本场 AI 知识小课堂老师教授了一些矩阵的相关知识,因为矩阵在 AI 人工智能中也有相当的 ...
- Lambda01 编程范式、lambda表达式与匿名内部类、函数式接口、lambda表达式的写法
1 编程范式 主要的编程范式有三种:命令式编程,声明式编程和函数式编程. 1.1 命令式编程 关注计算机执行的步骤,就是告诉计算机先做什么后做什么 1.2 声明式编程 表达程序的执行逻辑,就是告诉计算 ...
- Java入门第二季学习总结
课程总概 该门课程作为java入门学习的第二季,是在有一定的java基础上进行的进一步学习.由于该季涉及到了java的一些核心内容,所以相对第一季来说,课程难度有所提升.大致可将该季的课程分为五部分: ...
- [小北De编程手记] : Lesson 05 玩转 xUnit.Net 之 从Assert谈UT框架实践
这一篇,本文会介绍一下基本的断言概念,但重点会放在企业级单元测试的相关功能上面.下面来跟大家分享一下xUnit.Net的断言,主要涉及到以下内容: 关于断言的概念 xUnit.Net常用的断言 关于单 ...
随机推荐
- JAVA编程思想读书笔记(三)--RTTI
接上篇JAVA编程思想读书笔记(二) 第十一章 运行期类型判定 No1: 对于作为程序一部分的每个类,它们都有一个Class对象.换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当的说, ...
- 洛谷——P1349 广义斐波那契数列
题目描述 广义的斐波那契数列是指形如an=p*an-1+q*an-2的数列.今给定数列的两系数p和q,以及数列的最前两项a1和a2,另给出两个整数n和m,试求数列的第n项an除以m的余数. 输入输出格 ...
- 【JAVAWEB学习笔记】11_XML
今日内容介绍 编写服务器软件,访问指定配置内容 访问tomcat下已经发布的web项目 今日内容学习目标 可以编写xml存放任意内容 通过DTD约束编写指定格式的XML 通过Schema约束编写指定格 ...
- JSP中的9大内置对象四大域与servlet里的三大域
九大内置对象 隐式对象 说明 out 转译后对应JspWriter对象,其内部关联一个PringWriter对象 request 转译后对应HttpServletRequest/ServletRequ ...
- codevs 1044 拦截导弹 1999年NOIP全国联赛提高组
1044 拦截导弹 1999年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 某国为 ...
- [BZOJ4539][HNOI2016]树(主席树)
4539: [Hnoi2016]树 Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 746 Solved: 292[Submit][Status][D ...
- Codeforces 990G 点分治+暴力
题意:给出一棵点带权的树,求i\(\in\)[1,200000]所有路径的上点权的gcd==i的个数. 考虑点分治,对于一棵以u为根的子树,如何统计经过u的路径的答案? 显然既然是经过点u的路径,那么 ...
- 1.7(SQL学习笔记)游标
一.游标简介 SELECT语句得到的是一个结果集,有时我们需要对结果集中的单条数据进行处理. 这时就需要使用游标,游标定义时和一个SELECT语句的结果集关联在一起. 游标执行这个结果集,可以在结果集 ...
- noip2011初赛提高组 试题详解
转载自:https://blog.csdn.net/Eirlys_North/article/details/52889993 一.单项选择题(共20题,每题1.5分,共计30分,每题有且仅有一个正确 ...
- HDU 4612 Warm up tarjan 树的直径
Warm up 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=4612 Description N planets are connected by ...