multithreading - 如何进行原子交换——Scala方式?

标签 multithreading scala atomic

问题

我有这样的代码

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
  • 使用 STM(akka 或其他实现)。


  • 澄清:我缺乏有关您的问题限制的信息。但这是我的理解。

    多个线程必须同时遍历 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/

    相关文章:

    Scala 空默认闭包?

    scala - 在spark中导入TSV文件

    c# - "switch"语句评估线程安全吗?

    java - 将 <Object, AtomicInteger> 映射到关联数组

    c++ - 在类中正确使用 mutex、lock_guard、unique_lock

    Python 线程/队列问题

    当使用相机太慢时,Android 从横向移动到纵向(反之亦然)?

    java - Java 中的并行任务

    scala - 将 OOP "decorator"重构为释放 monad 结构

    c++ - STLR(B) 是否在 ARM64 上提供顺序一致性?