【问题标题】:What is the best way to manage mutable state?管理可变状态的最佳方法是什么?
【发布时间】:2012-12-10 15:23:52
【问题描述】:

我刚刚在 Coursera 上完成了 Martin Odersky 的 scala 课程。 Scala 是我的第一个 FP 语言,我对限制可变状态的想法感到兴奋。这允许更容易的并发性,也使代码超级可维护。

在学习所有这些的过程中,我意识到你可以保证一个对象的可变性,只要它没有可变变量并且只引用不可变对象。所以现在我可以通过创建新状态而不是修改旧状态来完成所有工作,尽可能使用尾递归。

太好了。所以我只能做到这一点。在某些时候,我的应用程序需要能够修改一些现有状态。我知道此时在哪里进行并发控制,锁,等等。我仍然默认使用我一直使用的标准多线程并发控制。

哦 scala 社区,有没有更好的方法?也许是单子?

编辑:这个问题有点笼统,所以我想给出一个用例: 我有一个机器学习算法,可以存储多个数据集合。它们具有返回数据(训练等)的更新表示的函数,所有这些都是不可变的。最终,我可以保持这种返回更新状态模式沿着链向上运行到运行模拟的实际对象。这具有可变的状态并保存对集合的引用。我可能想分发到多核或多系统。

【问题讨论】:

  • 隐藏actor内部的可变性
  • 我之前在一个项目中使用过akka actor,它做得非常好,并且非常适合在服务器之间分配负载。是否有适合本地规模的更轻量级演员?
  • 我觉得这个问题太主观了。也许对程序员更好。stackexchange
  • @ColinGodsey 还有 Scala Actors(与 Akka Actors 相对),标准 Scala 发行版附带的实现。预计未来会被Akkaactors替代,但你试过了吗?
  • @ColinGodsey:我也在使用 Scala 进行机器学习,我有一个关于不可变更新性能的related concern。我们都知道不变性的美妙之处,但在我看来,机器学习是一个完美的例子,其中可变数据结构有时非常易于使用和/或更快,因此请谨慎选择。

标签: scala


【解决方案1】:

这是一个有点主观的问题,所以我不会尝试回答其中的“哪个是最好的”部分。如果您主要关心的是多线程并发上下文中的状态,那么一个选项可能是Software Transactional Memory

Akka 提供了 STM 的 Implementation(参见快速入门)。根据您的用例,它可能是重量级的或矫枉过正的,但话又说回来,它可能比一堆锁更可取。与锁不同,STM 倾向于乐观,就像数据库事务一样。与数据库事务一样,您在事务上下文中显式更改共享状态,并且您描述的更改将自动提交或在检测到冲突时重新尝试。基本上,您必须将所有状态包装在 Refs 中,该状态只能在“原子”块中进行操作 - 实现为采用闭包的方法,您可以在其中使用操作 Refs 和 ScalaSTM 确保对你的状态的整套操作要么成功要么失败——不会有中途或不一致的变化。

这利用了 Scala 的 隐式参数 - 对 Refs 的所有操作都需要一个 transaction 对象作为参数,并且这是通过给 atomic 的闭包接收的并且可以声明为隐式,因此atomic 中的所有代码都可以以非常自然但安全的方式编写。

要做到这一点,您确实需要使用提供的事务性数据结构;所以这意味着使用TSet 而不是SetTMap 而不是Map。当在事务上下文中使用时(在 atomic 块中),这些提供了全有或全无的更新语义。这很像 clojure 的持久化集合。您还可以在 Refs 之外构建自己的事务数据结构,以便在这些 atomic 块中使用。

如果你不反感括号的话,对refs的clojure解释真的很好:http://clojure.org/refs

【讨论】:

  • 这里解释了 STM 的想法,并带有 Scala 示例。 ppl.stanford.edu/papers/scaladays2010bronson.pdf
  • STM 听起来真的很棒,我想这可能是我想要坚持的模型。这些模式也直接适用于数据网格或数据库的使用,因此它非常适合实现以后可能会分发的系统。
  • 如果您计划实现一个以后可能会分发的系统,那么单独使用 STM 可能没有帮助 - STM 作为一种在单个进程(单个JVM)。把 Akka 看成一个整体可能是一个不错的选择。尤其是交易者。
【解决方案2】:

根据您的用例,您可能能够坚持使用部分复制而不是实际改变它们的深度不可变对象结构(类似于与其原始列表共享后缀的“更新”不可变列表)。所谓的 lenses 是处理此类结构的好方法,请在 this SO questionthis blog post 中了解它们。

当然,坚持不可变结构只有在您不希望全局可观察到更改时才有效。不可变结构很可能不是一个选项的示例是两个并发客户端在共享列表上工作,其中客户端 A 所做的修改必须由客户端 B 观察,反之亦然。

【讨论】:

  • 我通常会尝试坚持深入的不可变结构,镜头听起来很有趣......我必须做更多的阅读,我不确定我是否真的掌握了这个概念
【解决方案3】:

我建议最好的方法是将可变变量存储在 Akka Actor 中,使用传入和传出 Akka Actor 的消息来发送和接收这个可变引用。使用不可变的数据结构。

我有一个如下的 StorageActor。每次通过 StoreEntity 存储某些内容时,都会更新变量 entityMap。此外,它不需要是易失性的并且仍然有效。

Akka actor 是可以改变事物的地方,消息在纯函数世界中传入和传出。

import akka.actor.Actor
import java.util.UUID
import com.orsa.minutesheet.entity.Entity

case class EntityRef(entity: Option[Entity])

case class FindEntity(uuid: UUID)
case class StoreEntity[T >: Entity](uuid: UUID, entity: Option[T])

class StorageActor extends Actor {

  private var entityMap = Map[UUID, Entity]()

  private def findEntityByUUID(uuid:UUID): Option[Entity] = entityMap.get(uuid)

  def receive = {
    case FindEntity(uuid) => sender ! EntityRef( findEntityByUUID(uuid) )
    case StoreEntity(uuid, entity) =>
      entity match {
        case Some(store) => entityMap += uuid -> store.asInstanceOf[Entity]
        case None => entityMap -= uuid
      }
  }
}

【讨论】:

    猜你喜欢
    • 2018-02-16
    • 2013-02-27
    • 1970-01-01
    • 2015-07-02
    • 1970-01-01
    • 2015-04-28
    • 2012-01-27
    • 1970-01-01
    • 2018-08-14
    相关资源
    最近更新 更多