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

Ott*_*tto 38 concurrency scala go

我多年来一直是Java开发人员,但在我开始进行Android开发之前从未过多地处理并发问题,并且突然开始发现"应用程序没有响应"和明显的死锁情况.

这让我意识到理解和调试其中一些并发问题是多么困难.Scala和Go等新语言如何提高并发性?它们如何更容易理解?它们如何防止并发错误?有人能提供展示优势的真实案例吗?

Rex*_*err 49

简化并发性的三个主要竞争者是参与者,软件事务内存(STM)和自动并行化.Scala实现了这三种功能.

演员

演员们在Erlang语言中找到了他们最着名的实现,据我所知,这个想法始于*.Erlang是从演员的角度设计的.这个想法是演员本身彼此是黑盒子; 它们只通过传递消息进行交互.

Scala在其库中具有actor的实现,并且外部库中提供了变体.在主库中,黑盒子没有强制执行,但是有一些易于使用的方法来传递消息,Scala可以很容易地创建不可变消息(所以你不必担心你发送一个带有一些内容的消息,然后在某个随机时间更改内容).

演员的优势在于您不必担心复杂的共享状态,这真正简化了所涉及的推理.此外,您可以将问题分解为比线程更小的部分,并让actor库弄清楚如何将actor捆绑到适当数量的线程中.

缺点是如果你想做一些复杂的事情,你需要有很多逻辑来处理发送消息,处理错误等等,然后再知道它成功了.

软件事务内存

STM基于这样的想法,即最重要的并发操作是获取一些共享状态,调整它并将其写回.所以它提供了这样做的手段; 但是,如果它遇到一些问题 - 它通常会延迟检测直到最后,此时它会检查以确保所有写入都正确 - 它会回滚更改并返回失败(或再次尝试).

这既是高性能的(在通常只有一切正常的情况下,因为通常一切都很好)并且对大多数锁定错误都很健壮,因为STM系统可以检测到问题(甚至可能会做一些事情,比如从较低的位置进行访问-priority请求并将其赋予更高优先级的请求.

与演员不同,只要你能处理失败,就更容易尝试复杂的事情.但是,您还必须正确推理潜在的状态; STM通过失败和重试来防止罕见的意外死锁,但是如果您只是出现了逻辑错误并且某些步骤无法完成,则STM无法允许它.

Scala有一个STM库,它不是标准库的一部分,但正在考虑包含它.Clojure和Haskell都拥有完善的STM库.

自动并行化

自动并行化认为您不想考虑并发性; 你只想要快速发生的事情.因此,如果您进行某种并行操作 - 例如,将一些复杂的操作应用于一组项目,一次一个,并生成一些其他集合 - 您应该拥有自动执行此操作的例程.Scala的集合可以这种方式使用(有一种.par方法可以将传统的串行集合转换为并行模拟).许多其他语言具有类似的功能(Clojure,Matlab等).


编辑:实际上,Actor模型在1973年被描述过,可能是由Simula 67中的早期工作(使用协程而不是并发)推动的; 1978年出现了相关的沟通顺序过程.因此,当时Erlang的功能并不是独一无二的,但该语言在部署actor模型时非常有效.


jim*_*imt 7

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

但是,应该注意的是,Go所有权的转移不以任何方式强制执行.通过渠道发送的对象不会被标记或标记或类似的东西.这只是一个惯例.因此,如果你是如此倾向,你可以通过改变你之前通过频道发送的值来射击自己.

Go的优势在于Go提供的语法(启动goroutine和通道工作方式),使得编写正确运行的代码变得更加容易,从而防止竞争条件和死锁.Go的清晰并发机制使得很容易推断出你的程序会发生什么.

作为旁注:如果您真的想使用它们,Go中的标准库仍然提供传统的互斥锁和信号量.但你显然是由你自行决定和风险.


Dan*_*mon 7

对我来说,使用Scala(Akka)actor比传统的并发模型有几个优点:

  1. 使用像actor这样的消息传递系统可以让您轻松处理共享状态.例如,我经常将一个可变数据结构包装在一个actor中,因此访问它的唯一方法是通过消息传递.由于参与者总是一次处理一条消息,因此可确保对数据的所有操作都是线程安全的.
  2. Actors部分消除了处理产生和维护线程的需要.大多数actor库都会处理跨线程分配的actor,所以你只需要担心启动和停止actor.通常我会创建一系列相同的actor,每个物理CPU核心一个,并使用负载均衡器actor将消息均匀地分发给它们.
  3. 演员可以帮助提高系统可靠性.我使用Akka actor,一个功能是你可以为actor创建一个supervisor,如果一个actor崩溃了,那么supervisor会自动创建一个新的实例.这可以帮助防止线程崩溃并且您遇到半运行程序的线程情况.根据需要调整新actor并与在另一个应用程序中运行的远程actor一起工作也很容易.

您仍然需要对并发性和多线程编程有一个正确的理解,因为死锁和竞争条件仍然存在,但是演员可以更容易地识别和解决这些问题.我不知道这些适用于Android应用程序有多少,但我主要做服务器端编程和使用actor使开发更容易.