在他的一个视频中(关于 Scala 的惰性求值,即 lazy
关键字),Martin Odersky 展示了 cons
的以下实现用于构造 Stream
的操作:
def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
def head = hd
lazy val tail = tl
...
}
所以
tail
操作是使用语言的惰性求值特性简洁地编写的。但实际上(在 Scala 2.11.7 中),
tail
的实现不那么优雅:@volatile private[this] var tlVal: Stream[A] = _
@volatile private[this] var tlGen = tl _
def tailDefined: Boolean = tlGen eq null
override def tail: Stream[A] = {
if (!tailDefined)
synchronized {
if (!tailDefined) {
tlVal = tlGen()
tlGen = null
}
}
tlVal
}
双重检查锁定和两个 volatile 字段:这大致是您在 Java 中实现线程安全延迟计算的方式。
所以问题是 :
lazy
Scala 的关键字在多线程情况下提供任何“评估最大一次”保证? tail
实现在 Scala 中进行线程安全的惰性求值的惯用方法? 最佳答案
Doesn't lazy keyword of Scala provide any 'evaluated maximum once' guarantee in a multi-threaded case?
是的,正如其他人所说的那样。
Is the pattern used in real tail implementation an idiomatic way to do a thread-safe lazy evaluation in Scala?
编辑:
我想我有实际的答案,为什么不
lazy val
. Stream
具有面向公众的 API 方法,例如 hasDefinitionSize
继承自 TraversableOnce
.为了知道是否Stream
有一个有限的大小不是,我们需要一种检查方法而不具体化底层 Stream
尾部。自 lazy val
实际上并没有暴露底层,我们不能这样做。这是由 SI-1220 支持的
为了加强这一点,@Jasper-M 指出新的
LazyList
稻草人中的 api(Scala 2.13 集合改造)不再有这个问题,因为整个集合层次结构已经重新设计,不再有这样的问题。性能相关问题
我会说“这取决于”您从哪个角度看待这个问题。从 LOB 的角度来看,我肯定会说
lazy val
为了实现的简洁和清晰。但是,如果您从 Scala 集合库作者的角度来看它,事情就会开始变得不同。这样想,您正在创建一个可能被许多人使用并在世界各地的许多机器上运行的库。这意味着您应该考虑每个结构的内存开销,尤其是当您自己创建这样一个基本数据结构时。我这样说是因为当你使用
lazy val
时,按照设计,您会生成一个额外的 Boolean
标记值是否已初始化的字段,我假设这是库作者旨在避免的。 Boolean
的大小在 JVM 上当然依赖于 VM,即使是一个字节也是需要考虑的,尤其是当人们生成大 Stream
时。 s 的数据。同样,这绝对不是我通常会考虑的事情,绝对是对内存使用的微优化。我认为性能是这里的关键点之一的原因是 SI-7266它修复了 Stream 中的内存泄漏。请注意跟踪字节码以确保在生成的类中没有保留额外的值是多么重要。
实现上的区别在于
tail
的定义是否被初始化是一个检查生成器的方法实现:def tailDefined: Boolean = tlGen eq null
而不是类上的字段。
关于multithreading - Scala Stream尾部懒惰和同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48111320/