我的核心问题是:如何在对象实例和方法参数组合的方法中实现同步?
以下是我的情况详情。我正在使用以下代码来实现memoization,改编自this answer:
/**
* Memoizes a unary function
* @param f the function to memoize
* @tparam T the argument type
* @tparam R the result type
*/
class Memoized[-T, +R](f: T => R) extends (T => R) {
import scala.collection.mutable
private[this] val cache = mutable.Map.empty[T, R]
def apply(x: T): R = cache.getOrElse(x, {
val y = f(x)
cache += ((x, y))
y
})
}
在我的项目中,我正在记住Future
以重复删除异步API调用。当使用for...yield
映射由标准ExcecutionContext
创建的结果期货时,这种情况正常,但是当我升级到Scala Async以更好地处理这些期货时。但是,我意识到库使用的多线程允许多个线程进入apply
,从而打败memoization,因为async
阻止所有并行执行,在cache
之前输入“orElse”thunk使用新的Future
进行更新。
要解决此问题,我将主要的apply函数放在this.synchronized
块中:
def apply(x: T): R = this.synchronized {
cache.getOrElse(x, {
val y = f(x)
cache += ((x, y))
y
})
}
这恢复了记忆行为。缺点是这将阻止具有不同参数的调用,至少在创建Future
之前。我想知道是否有办法在Memoized
实例和x
参数值apply
的组合上设置更细粒度的同步。这样,只会阻止重复数据删除的呼叫。
作为旁注,我不确定这是否真的对性能至关重要,因为一旦Future
被创建并返回(我认为?),同步块将会释放。但是,如果我对此没有任何顾虑,我也想知道。
答案 0 :(得分:1)
Akka演员结合期货提供了一种强大的方式来包裹可变状态而不会阻塞。这是一个如何使用Actor进行记忆的简单示例:
import akka.actor._
import akka.util.Timeout
import akka.pattern.ask
import scala.concurrent._
import scala.concurrent.duration._
class Memoize(system: ActorSystem) {
class CacheActor(f: Any => Future[Any]) extends Actor {
private[this] val cache = scala.collection.mutable.Map.empty[Any, Future[Any]]
def receive = {
case x => sender ! cache.getOrElseUpdate(x, f(x))
}
}
def apply[K, V](f: K => Future[V]): K => Future[V] = {
val fCast = f.asInstanceOf[Any => Future[Any]]
val actorRef = system.actorOf(Props(new CacheActor(fCast)))
implicit val timeout = Timeout(5.seconds)
import system.dispatcher
x => actorRef.ask(x).asInstanceOf[Future[Future[V]]].flatMap(identity)
}
}
我们可以像:
一样使用它val system = ActorSystem()
val memoize = new Memoize(system)
val f = memoize { x: Int =>
println("Computing for " + x)
scala.concurrent.Future.successful {
Thread.sleep(1000)
x + 1
}
}
import system.dispatcher
f(5).foreach(println)
f(5).foreach(println)
"计算5"只打印一次,但" 6"将打印两次。
有一些可怕的asInstanceOf
调用,但它完全是类型安全的。