Scala中的并发原语

Scala中的并发原语,scala,concurrency,akka,atomic,Scala,Concurrency,Akka,Atomic,对于访问受CPU限制的对象(没有IO和网络),什么是好的并发原语 例如,有一个FooCounter,它有一个方法get()、set()和inc(),用于var counter:Int,它在成千上万的线程之间共享 object FooCounter{ var counter: Int = 0; def get() = counter def set(value: Int) = {counter = counter + value} def inc() = counter + 1

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

例如,有一个FooCounter,它有一个方法get()、set()和inc(),用于var counter:Int,它在成千上万的线程之间共享

object FooCounter{
  var counter: Int = 0;
  def get() = counter
  def set(value: Int) = {counter = counter + value} 
  def inc() = counter + 1
}
我发现大多数关于Scala的文献都是关于Akka的。对我来说,演员模型似乎并不真正适合这个任务

也有未来/承诺,但它们有助于阻止任务

在Java中有一个很好的原始原子,它使用锁存器,对于这个任务来说,锁存器是非常健壮的

更新:
我可以使用Java原语来完成这个简单的任务。然而,我的目标是在这个简单的示例中使用和学习Scala并发模型。

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

对于更复杂的任务,我个人喜欢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原语。

您对Scala中“Akka方向”的看法是正确的:总而言之,我认为Scala语言开发人员社区和Akka社区有相当多的重叠。Scala的最新版本依赖于Akka参与者的并发性,但坦率地说,我看不出这里面有什么不好的地方

关于你的问题,请引用《阿克卡行动》一书:

参与者非常适合处理许多消息、捕获状态和数据 根据收到的信息以不同的行为作出反应”

“当你更愿意使用函数和 不需要对象来完成任务”


Future是一个尚未可用的值的占位符,因此,即使它用于非阻塞任务,它的用处也不止于此,我认为这可能是您在这里的选择。

您的任务的一个可能解决方案是actors。它不是最快的解决方案,而是安全和简单的:

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
  }
}

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

这是异步的,并保证了操作的顺序性。语义很好,如果您需要更高的性能,您可以随时将其更改为良好的旧
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))
    }
  }
}

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

  • synchronized
    语句(Scala
    synchronized
    语句类似于)
  • STMs(例如)
  • 可能,您可以使用Akka actor作为计数器,但请注意,这并不是actor最直接的应用程序
其他Scala并发实用程序,如futures和promises、并行集合或反应式扩展,并不最适合实现并发计数器,它们有不同的用法

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

标杆管理 要评估实现的运行时间和效率,一个好的选择是。网站上的在线文档包含了关于如何进行基准测试的详细示例

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

我认为这本书对你来说可能是一本好书。它包含了Scala中不同并发风格的详细文档,以及何时使用的说明。第5章和第9章也讨论了基准测试。 具体来说,并发计数器实用程序在本书第9章中进行了描述、优化和可扩展


免责声明:我是作者。

java.util.concurrent.atomic.AtomicInteger与Scala配合得非常好,使计数线程安全,并且您不必在项目中添加额外的依赖项。

同意,我可以使用java原语,但我的目标是使用Scala API并学习它们。谢谢您的回答。顺便问一下,这是一个好方法吗选择使计数器不可变并使用CopyOnWrite模式进行设置?我需要更精确地了解Akka。我对Akka的理解是,为这个简单的任务设置Akka群集和路由非常麻烦。请您详细说明未来如何保护我的价值不受数千个线程的影响?如果您只是在运行,则不需要群集或路由同一JVM中的并发任务。这就是为什么
akka集群
akka远程处理
与核心
akka actor
库是独立的依赖项。还没有足够的时间给出完整的答案,但你是对的,akka并不是真正适合这个问题的。看看-它实现了软件事务性内存,这正好适用于这种情况。我认为它将被添加到Scala标准库中。“数百万线程”…当然感谢您的回答。我已经预订了一本书。期待着它何时可用。仅供参考的代理已被弃用
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))
    }
  }
}