Scala中的并发原语
对于访问受CPU限制的对象(没有IO和网络),什么是好的并发原语 例如,有一个FooCounter,它有一个方法get()、set()和inc(),用于var counter:Int,它在成千上万的线程之间共享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
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
方法,则无法保证计数是准确的。您应该使用以下实用程序之一实现正确的并发计数器:
语句(Scalasynchronized
语句类似于)synchronized
- STMs(例如)
- 可能,您可以使用Akka actor作为计数器,但请注意,这并不是actor最直接的应用程序
- 及
免责声明:我是作者。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))
}
}
}