用于访问受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模型。
答案 0 :(得分:11)
您应该知道FooCounter
的实现不是线程安全的。如果多个线程同时调用get
,set
和inc
方法,则无法保证计数准确。您应该使用以下实用程序之一来实现正确的并发计数器:
synchronized
语句(Scala synchronized
语句类似于one in Java)其他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很好地配合,使计数线程安全,并且您不必在项目中添加其他依赖项。