问题
我有这样的代码
var ls = src.iter.toList
src.iter = ls.iterator
(这是我的迭代器包装器的复制构造函数的一部分)它读取源迭代器,并在下一行将其设置回来。问题是,这两行必须是原子的(特别是如果您认为我更改了复制构造函数的源——我不喜欢它,但是……)。
我读过有关 Actors 的文章,但我看不出它们如何适合这里——它们看起来更像是一种异步执行机制。我已经阅读了 Java 解决方案并在 Scala 中使用它们,例如:http://naedyr.blogspot.com/2011/03/atomic-scala.html
我的问题是:使某些操作原子化的最 Scala 方式是什么?我不想为此使用一些重型火炮,也不想使用一些外部资源。换句话说——看起来和感觉“正确”的东西。
我有点喜欢上面链接中提出的解决方案,因为这正是我所做的——交换引用。如果我理解正确,我只会保护那 2 行,其他代码不必更改!但我会等待明确的答案。
背景
因为每第 N 个问题,而不是答案我读“但你为什么使用...”,在这里:
How to copy iterator in Scala? :-)
我需要复制迭代器(制作一个 fork),这样的解决方案是我读到的最“正确”的解决方案。问题是,它破坏了原来的迭代器。
解决方案
锁具
例如这里:
http://www.ibm.com/developerworks/java/library/j-scala02049/index.html
我在这里看到的唯一问题是,我必须锁定这两行,以及 iter 上的所有其他用法。现在是小事,但是当我添加一些代码时,很容易忘记添加额外的锁。
我不是说“不”,但我没有经验,所以我想从熟悉 Scala 的人那里得到答案,指出一个方向——从长远来看,哪种解决方案最适合此类任务。
不可变迭代器
虽然我很欣赏 Paradigmatic 的解释,但我不明白这种方法如何适合我的问题。问题是 IteratorWrapper 类必须包装迭代器——即原始迭代器应该隐藏在类中(通常是通过将其设为私有(private)来完成的)。像 hasNext() 和 next() 这样的方法也应该被包装。通常 next() 会改变对象(迭代器)的状态,因此在不可变 IteratorWrapper 的情况下,它应该返回新的 IteratorWrapper 和 next() 的状态(成功与否)。如果 raw next() 失败,另一种解决方案将返回 NULL,无论如何,这使得使用这样的 IteratorWrapper 不是很方便。
更糟糕的是,仍然没有简单的方法来复制这样的 IteratorWrapper。
所以要么我错过了一些东西,要么实际上使代码原子化的经典方法更干净。因为所有的负担都包含在类中,并且用户不必为 IteratorWrapper 处理数据的方式(在这种情况下为原始迭代器)付出代价。
最佳答案
Scala 的方法是尽可能支持不变性(而且通常是可能的)。然后你不再需要复制构造函数、锁、互斥锁等。
例如,您可以将迭代器转换为 List
在对象构造中。由于列表是不可变的,您可以安全地共享它们而无需锁定:
class IteratorWrapper[A]( iter: Iterator[A] ) {
val list = iter.toList
def iteratorCopy = list.iterator
}
在这里,
IteratorWrapper
也是不可变的。您可以安全地传递它。但是如果你真的需要改变包装的迭代器,你将需要更苛刻的方法。例如,您可以:Actor
澄清:我缺乏有关您的问题限制的信息。但这是我的理解。
多个线程必须同时遍历
Iterator
.一种可能的方法是在将引用传递给线程之前复制它。但是,Scala 实践旨在共享不需要复制的不可变对象(immutable对象)。使用复制策略,您将编写如下内容:
//A single iterator producer
class Producer {
val iterator: Iterator[Foo] = produceIterator(...)
}
//Several consumers, living on different threads
class Consumer( p: Producer ) {
def consumeIterator = {
val iteratorCopy = copy( p.iterator ) //BROKEN !!!
while( iteratorCopy.hasNext ) {
doSomething( iteratorCopy.next )
}
}
}
但是,实现线程安全的复制方法是困难的(或缓慢的)。使用不变性的可能解决方案是:
class Producer {
val lst: List[Foo] = produceIterator(...).toList
def iteratorCopy = list.iterator
}
class Consumer( p: Producer ) {
def consumeIterator = {
val iteratorCopy = p.iteratorCopy
while( iteratorCopy.hasNext ) {
doSomething( iteratorCopy.next )
}
}
}
制作人将调用
produceIterator
一次在施工。它是不可变的,因为它的状态只是一个列表,它也是不可变的。 iteratorCopy
也是线程安全的,因为在创建副本时不会修改列表(因此多个线程可以同时遍历它而无需锁定)。请注意,调用
list.iterator
不遍历列表。所以它不会以任何方式降低性能(而不是每次都真正复制迭代器)。
关于multithreading - 如何进行原子交换——Scala方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7820316/