在实践中,消息传递并发语言如何比共享内存并发语言更好

我一直是一个Java开发人员多年,但直到我开始进行Android开发,从来没有处理太多的并发问题,突然开始发现“应用程序没有响应”和明显的死锁情况。

这让我意识到理解和debugging这些并发问题是多么困难。 Scala和Go等新语言如何提高并发性? 他们如何更容易理解,他们如何防止并发错误? 有人可以提供真实世界的例子,certificate优势?

简化并发的三个主要竞争者是angular色,软件事务内存(STM)和自动并行化。 斯卡拉有三个实现。

演员

演员发现他们最显着的执行语言Erlang,据我所知是的想法开始*。 Erlang是基于演员devise的。 这个想法是,演员本身是彼此黑盒子; 他们只通过传递消息进行交互。

Scala在库中有一个执行者的实现,在外部库中有变种。 在主库中,黑盒不是强制执行的,但有传递消息的简单易用的方法,而且Scala可以很容易地创build不可变的消息(所以你不必担心发送消息与一些内容,然后在一些随机时间更改内容)。

演员的优点是你不必担心复杂的共享状态,这真的简化了所涉及的推理。 此外,您可以将问题分解为比线程更小的部分,并让angular色库找出如何将angular色绑定到适当数量的线程。

缺点是,如果你想要做一些复杂的事情,在你知道它成功之前,你有很多逻辑来处理发送消息,处理错误等等。

软件事务内存

STM的基本思想是,最重要的并发操作是获取一些共享状态,摆弄它,然后写回来。 所以它提供了一个这样做的手段; 但是,如果遇到一些问题 – 通常会延迟检测直到最终检测到确保写入全部正确 – 它会回滚更改并返回失败(或再次尝试)。

这既是高性能 (在只有适度争用的情况下,因为通常一切都很好),而且对于大多数locking错误都是有效的 ,因为STM系统可以检测到问题(甚至可能做一些事情,比如从低端优先级请求并将其提供给更高优先级的请求)。

不像演员,只要你能处理失败,试试复杂的东西就容易多了。 但是,你也必须正确地理解底层的状态。 STM通过失败和重试来防止罕见的无意的死锁,但是如果你只是犯了一个逻辑错误,而某些步骤不能完成,STM就不能允许它。

斯卡拉有一个STM库,不是标准库的一部分,但正在考虑列入。 Clojure和Haskell都有完善的STM库。

自动并行化

自动并行化认为您不想考虑并发性; 你只是想让事情发生很快。 所以,如果你有某种并行操作 – 例如对一个项目集合应用一些复杂的操作,并产生一些其他的集合,那么你应该有自动并行执行的例程。 Scala的集合可以以这种方式使用(有一个.par方法将常规的串行集合转换为并行模拟)。 许多其他语言具有相似的function(Clojure,Matlab等)。


编辑:实际上, Actor模型早在1973年就被描述过了,可能是Simula 67早期工作的动机(使用协程而不是并发)。 在1978年来到了相关的沟通顺序过程 。 所以Erlang的function在当时并不是独一无二的,但是这种语言在部署演员模型方面是独一无二的。

对我来说,使用Scala(Akka)的演员已经比传统的并发模型有了几个优势:

  1. 使用像actor这样的消息传递系统可以轻松处理共享状态。 例如,我会经常在一个actor中包装一个可变数据结构,所以访问它的唯一方法就是通过消息传递。 由于参与者总是一次处理一条消息,这确保了对数据的所有操作都是线程安全的。
  2. 行为者部分消除了处理产卵和维护线程的需要。 大多数演员图书馆将处理跨线程分配angular色,所以你只需要担心启动和停止angular色。 通常我会创build一系列相同的angular色,每个物理CPU核心一个,并使用负载均衡参与者均匀分配消息给他们。
  3. 参与者可以帮助提高系统的可靠性。 我使用阿卡演员,一个特点是你可以为演员创build一个主pipe,如果一个演员崩溃了,主pipe会自动创build一个新的实例。 这可以帮助阻止线程崩溃的情况,而且您正在使用一个半运行的程序。 根据需要调整新angular色也很容易,并且可以与在另一个应用程序中运行的远程angular色一起工作。

您仍然需要对并发和multithreading编程有一个体面的理解,因为死锁和竞态条件仍然是可能的,但参与者可以更容易地识别和解决这些问题。 我不知道这些应用程序适用于Android应用程序,但我主要进行服务器端编程,使用演员使开发更容易。

在一个惯用的Go程序中,线程通过通道传递状态和数据。 这可以在不需要锁的情况下完成。 将数据通过通道传递给接收方意味着传输数据的所有权。 一旦你通过一个频道发送一个价值,你不应该再对它进行操作了,因为现在接收它的人“拥有”它。

但是,应该注意的是,“运行时”不会强制执行“所有权”的转移。 通过频道发送的对象没有标记或标记或类似的东西。 这只是一个惯例。 所以你可以,如果你是如此倾向于通过改变你以前通过频道发送的价值来击败自己。

Go的优势在于Go提供的语法(启动goroutines和通道工作方式),使得编写正确运行的代码变得容易很多,从而防止竞态条件和死锁。 Go的清晰的并发机制让你很容易理解你的程序将会发生什么。

作为一个方面说明:如果你真的想使用它,Go中的标准库仍然提供传统的互斥和信号量。 但是,你显然是自行决定并承担风险的。

斯卡拉的演员工作在一个无共享的原则,所以没有锁(因此没有死锁)! 演员听消息,并被代码调用,这些代码有一些演员可以使用的东西。