Akka中最重要的即是actor模型。html
几十年前,Carl Hewitt提出了actor模型,以做为在高性能网络下并行处理的一种方式。可是在当时并无这样的环境,现在硬件和基础设施能力已经遇上并超越了Carl Hewitt当时的预料。因此,那些想构建高性能分布式系统的组织遇到的使用面向对象编程(OOP)模型没法彻底解决的挑战,如今可使用actor模型解决。编程
现在,actor模型不只被认为是一种高效的解决方案,并且也已经在世界上一些要求苛刻的应用中获得了验证。为了突出actor模型所能解决的问题,本主题主要讨论传统编程思想与现代多线程多CPU架构之间的不匹配:后端
OOP的核心是封装。封装规定对象内的数据不能直接从外部访问,只能调用方法来进行修改。对象须要暴露出一些安全的操做,这些安全操做用来维护其内部数据的约束。缓存
例如,对有序二叉树的操做不得违反二叉树有序的约束。调用者但愿排序是完整的,当查询树中某个数据时,他们须要可以依赖这个约束。安全
当咱们分析OOP运行时行为时,咱们有时会绘制一个时序图,显示方法调用的交互。微信
不幸的是,上图并不能准确地表示执行期间实例的生命周期。实际上,全部这些调用发生在同一线程上。网络
当您尝试模拟多线程状况时,上面的这种表达方式就变得更加清晰了。由于咱们能够经过下图来表示两个线程访问同一个实例:多线程
两个线程进入同一个对象相同的方法,可是对象的封装模型并不能很好的表达这其中发生的事情。两个线程能够以任意方式交错,想象一下,若是是多线程,这个问题会更加严重。架构
解决此问题的经常使用方法是给这些方法加锁。虽然这确保了在任何给定时间最多只有一个线程将进入该方法,但这是一种很是昂贵的策略:并发
这些现实致使了一种尴尬的局面:
此外,锁只能在本地很好地工做。在协调跨多台机器时,惟一的选择是分布式锁。不幸的是,分布式锁的效率比本地锁效率要差几个级别,而且在扩展时有更多的限制。分布式锁须要在多台计算机上经过网络进行屡次通讯往返,所以还存在延迟。
在面向对象语言中,咱们不多考虑线程的执行路径。咱们常常将系统设想为一个由对象组织成的网络,它们对方法调用做出反应,并修改其内部状态,而后经过方法调用相互通讯,从而驱动整个应用程序运行。
可是,在多线程分布式环境中,其实是线程经过方法调用“遍历”此对象网络。所以,真正推进应用程序运行的是线程:
总结:
在80-90年代的编程概念模型中,本地变量是直接写入到内存中的(这和咱们理解的本地变量是存在寄存器中是不同的)。在现代架构上,CPU是写入到缓存行而不是直接写入内存的。这些高速缓存大多数都在CPU内核中,也就是说,一个内核的写入不会被另外一个内核看到。为了使内核中的本地更改对另外一个核心可见,须要将缓存行传送到另外一个核心中。
在JVM中,咱们必须使用volatile标记或使用Atomic包装类明确表示变量要跨线程进行内存共享。不然,咱们只能先加锁而后访问它们。为何咱们不将全部变量都标记为volatile?由于跨核心同步缓存行是一项很是昂贵的操做!这样作会阻止内核去执行额外的工做,并致使缓存一致性协议出现瓶颈。
即便对于了解这种状况的开发人员来讲,肯定哪些变量应该被标记为volatile,或者使用哪一种atomic结构也是一种艺术。
总结:
今天,咱们将调用栈视为理所固然。可是,它们是在一个并发编程并不重要的时代发明的,由于那时多CPU系统并不常见,调用栈不会跨线程。
当主线程打算将任务委托给“后台”时,这实际上就是将任务委托给另一个工做线程,实际上就是主线程将一个任务对象放入工做线程中的一个共享队列里,工做线程负责从这个队列里获取任务来执行,这就容许主线程继续前进并执行其余任务。
他的第一个问题是,工做线程如何通知主线程任务已完成?当任务因异常而失败时会出现更严重的问题,异常传播到哪里?真实状况是它将传播到工做线程的异常处理程序,而后彻底忽略了实际的“调用者”是主线程:
这是一个严重的问题。主线程的调用栈上不能捕获这个异常,工做线程如何处理这种状况?须要以某种方式通知主线程,例如将异常放在主线程预先准备存放结果的地方,但若是主线程一直没有收到通知,任务也即丢失!
当工做线程在执行任务时出现BUG,致使工做线程关闭,这时谁来从新启动一个线程来处理这个任务,而且该任务如何恢复到正常的状态。这都是问题。
总结:
原文为akka官网连接:doc.akka.io/docs/akka/c…
接下来的文章,让咱们看看actor模型如何克服这些挑战。
若是以为这篇文章能让你学到知识,可否帮忙转发,将知识分享出去。 若是想第一时间学习更多的精彩的内容,请关注微信公众号:1点25