Scala中的并发原语

时间:2014-11-13 06:39:08

标签: scala concurrency akka atomic

用于访问受CPU限制的对象(没有IO和网络)的良好并发原语是什么?

例如,有一个FooCounter,它有一个方法get(),set()和inc()用于var计数器:Int,它们在成千上万的线程中共享。

object FooCounter{
  var counter: Int = 0;
  def get() = counter
  def set(value: Int) = {counter = counter + value} 
  def inc() = counter + 1
}

我发现大多数关于Scala的文献都是针对Akka的。对我来说,似乎Actor模型并不适合这项任务。

还有期货/承诺,但它们有利于阻止任务。

在Java中,有一个很好的原始Atomics,它使用锁存器,这对于这个任务非常强大和下降。

更新 我可以使用Java原语来完成这个简单的任务。但是,我的目标是在这个简单的例子中使用和学习Scala Concurrency模型。

6 个答案:

答案 0 :(得分:11)

您应该知道FooCounter的实现不是线程安全的。如果多个线程同时调用getsetinc方法,则无法保证计数准确。您应该使用以下实用程序之一来实现正确的并发计数器:

  • synchronized语句(Scala synchronized语句类似于one in Java
  • atomic variables
  • STM(例如,ScalaSTM
  • 可能,你可以使用一个作为计数器的Akka演员,但请注意,这不是演员最直接的应用

其他Scala并发实用程序,例如期货和承诺,并行集合或反应式扩展,不是最适合实现并发计数器,并且具有不同的用法。

您应该知道Scala在某些情况下会重用Java并发基础结构。例如 - Scala不提供自己的原子变量,因为Java原子类已经完成了这项工作。相反,Scala旨在提供更高级别的并发抽象,例如actor,STM和异步事件流。

基准

要评估实施的运行时间和效率,一个不错的选择是ScalaMeter。该网站的在线文档包含有关如何进行基准测试的详细示例。

文档和并发库

虽然Akka是最受欢迎且记录最好的Scala并发实用程序,但还有许多其他高质量的实现,其中一些更适合于不同的任务:

我认为Learning Concurrent Programming in Scala对你来说可能是一本好书。它包含Scala中不同并发样式的详细文档,以及何时使用哪些样式的说明。第5章和第9章也涉及基准测试。 具体而言,并发计数器实用程序在本书的第9章中进行了描述,优化和扩展。

免责声明:我是作者。

答案 1 :(得分:4)

您可以使用所有Java合成化原语,例如AtomicInteger进行计数。

对于更复杂的任务,我个人喜欢scala-stm库:http://nbronson.github.io/scala-stm/

使用STM,您的示例将如下所示

object FooCounter{
  private val counter = Ref(0);
  def get() = atomic { implicit txn => counter() }
  def set(value: Int) = atomic { implicit txn => counter() = counter() + value } 
  def inc() = atomic { implicit txn => counter() = counter() + 1 }
}

然而,对于这个简单的例子,我将停止Java原语。

答案 2 :(得分:3)

你对Scala中的“Akka方向”是正确的:总而言之,我认为Scala语言开发者社区与Akka之间有很多重叠。 Scala的后期版本依赖于Akka actor来实现并发性,但坦率地说,我看不出这一点很糟糕。

关于你的问题,引用“Akka in Action”一书:

  

演员非常适合处理许多消息,捕获状态和   根据收到的消息对不同的行为做出反应“

  

“期货是你宁愿使用函数时使用的工具   不需要对象来完成这项工作“

A Future是一个尚未提供的值的占位符,所以即使它用于非阻塞任务,它也不仅仅是有用的,我认为这可能是你的选择。

答案 3 :(得分:2)

你的任务的一个可能的解决方案是演员。它不是最快的解决方案,但安全而简单:

sealed trait Command
case object Get extends Command
case class Inc(value: Int) extends Command

class Counter extends Actor {
  var counter: Int = 0

  def receive = {
    case Get =>
      sender ! counter
    case Inc(value: Int) =>
      counter += value
      sender ! counter
  }
}

答案 4 :(得分:2)

如果您不愿意直接使用java.util.concurrent,那么您最优雅的解决方案可能就是使用Akka Agents

import scala.concurrent.ExecutionContext.Implicits.global
import akka.agent.Agent

class FooCounter {
  val counter = Agent(0)
  def get() = counter()
  def set(v: Int) = counter.send(v)
  def inc() = counter.send(_ + 1)
  def modify(f: Int => Int) = counter.send(f(_))
}

这是异步的,并保证操作的顺序性。语义很好,如果你需要更多的性能,你总是可以把它改成好的java.util.concurrent类似物:

import java.util.concurrent.atomic.AtomicInteger

class FooCounter {
  val counter = new AtomicInteger(0)
  def get() = counter.get()
  def set(v: Int) = counter.set(v)
  def inc() = counter.incrementAndGet()
  def modify(f: Int => Int) = {
    var done = false
    var oldVal: Int = 0
    while (!done) {
      oldVal = counter.get()
      done = counter.compareAndSet(oldVal, f(oldVal))
    }
  }
}

答案 5 :(得分:0)

java.util.concurrent.atomic.AtomicInteger与Scala很好地配合,使计数线程安全,并且您不必在项目中添加其他依赖项。