我多年来一直是Java开发人员,但在我开始进行Android开发之前从未必须处理过多的并发问题,并且突然开始发现“应用程序没有响应”和明显的死锁情况。
这让我意识到理解和调试其中一些并发问题是多么困难。 Scala和Go等新语言如何提高并发性?它们如何更容易理解?它们如何防止并发错误?有人能提供展示优势的真实案例吗?
答案 0 :(得分:48)
简化并发性的三个主要竞争者是参与者,软件事务内存(STM)和自动并行化。 Scala实现了这三种功能。
演员在Erlang语言中找到他们最值得注意的实现,据我所知,这个想法始于*。 Erlang是从演员的角度设计的。这个想法是演员本身彼此是黑盒子;它们只通过传递消息进行交互。
Scala在其库中有一个actor的实现,并且外部库中提供了变体。在主库中,黑盒子没有强制执行,但是有一些易于使用的方法来传递消息,Scala可以很容易地创建不可变消息(所以你不必担心你发送一个带有一些内容的消息,然后在某个随机时间更改内容)。
演员的优势在于您不必担心复杂的共享状态,这实际上简化了所涉及的推理。此外,您可以将问题分解为比线程更小的部分,并让actor库弄清楚如何将actor捆绑到适当数量的线程中。
缺点是如果你想要做一些复杂的事情,那么在你知道它成功之前,你需要有很多逻辑来处理发送消息,处理错误等等。
STM基于这样一种观点,即最重要的并发操作是获取某些共享状态,将其调整并将其写回。所以它提供了这样做的手段;但是,如果它遇到一些问题 - 它通常会延迟检测直到最后,此时它会检查以确保所有写入都正确 - 它会回滚更改并返回失败(或再次尝试)。
这是高性能(在只有适度争用的情况下,因为通常情况一切都很好)并且对大多数锁定错误都是健壮的,因为STM系统可以检测问题(甚至可能做一些事情,例如从较低优先级的请求中获取访问权限并将其提供给更高优先级的请求)。
与演员不同,只要你能处理失败,就更容易尝试复杂的事情。但是,您还必须正确推理潜在的状态; STM通过失败和重试来防止罕见的意外死锁,但是如果您只是出现了逻辑错误并且某些步骤无法完成,则STM无法允许它。
Scala有一个STM库,它不是标准库的一部分,但正在考虑包含它。 Clojure和Haskell都有完善的STM库。
自动并行化认为您不想考虑并发性;你只想要快速发生的事情。因此,如果您进行某种并行操作 - 例如,将一些复杂的操作应用于一组项目,一次一个,并生成一些其他集合 - 您应该拥有自动执行此操作的例程。 Scala的集合可以这种方式使用(有一种.par
方法可以将传统的串行集合转换为并行模拟。许多其他语言具有类似的功能(Clojure,Matlab等)。
编辑:实际上,Actor model在1973年被描述过,可能是因为早期在Simula 67中的工作(使用协程而不是并发);在1978年出现了相关的Communicating Sequential Processes。因此,当时Erlang的功能并不是独一无二的,但该语言在部署actor模型时非常有效。
答案 1 :(得分:6)
在惯用的Go程序中,线程通过通道传递状态和数据。 这可以在不需要锁的情况下完成。将数据通过信道传递给接收器意味着转移数据的所有权。一旦你通过一个频道发送了一个价值,就不应再对它进行操作,因为现在收到它的人“拥有”它。
但是,应该注意的是,Go所有权的转移并不以任何方式强制执行。通过渠道发送的对象不会被标记或标记或类似的东西。这只是一个惯例。所以你可以,如果你是如此倾向,可以通过改变你之前通过频道发送的值来射击自己。
Go的优势在于Go提供的语法(启动goroutine和通道工作方式),使编写正确运行的代码变得更加容易,从而防止竞争条件和死锁。 Go的清晰并发机制使得很容易推断出你的程序会发生什么。
作为旁注:如果您真的想使用它们,Go中的标准库仍然提供传统的互斥锁和信号量。但是你显然是由你自行决定并冒风险。
答案 2 :(得分:6)
对我来说,使用Scala(Akka)actor比传统的并发模型有几个优点:
你仍然需要对并发和多线程编程有一个正确的理解,因为死锁和竞争条件仍然存在,但是演员可以更容易地识别和解决这些问题。我不知道这些适用于Android应用程序有多少,但我主要进行服务器端编程,使用actor使开发变得更加容易。
答案 3 :(得分:0)
Scala actor遵循无共享原则,因此没有锁(因此没有死锁)!演员监听消息,并由具有某些东西的代码调用。