Как сделать атомный обмен - способ Scala? - PullRequest
5 голосов
/ 19 октября 2011

Задача

У меня есть такой код

var ls = src.iter.toList
src.iter = ls.iterator

(это часть конструктора копирования моего итератора-обертки), который читает исходный итератор и в следующей строке устанавливает его обратно. Проблема в том, что эти две строки должны быть атомарными (особенно если учесть, что я меняю источник конструктора копирования - мне это не нравится, но хорошо ...).

Я читал об актерах, но не вижу, как они здесь подходят - они больше похожи на механизм асинхронного выполнения. Я читал о решениях Java и их использовании в Scala, например: http://naedyr.blogspot.com/2011/03/atomic-scala.html

Мой вопрос: какой самый Scala способ сделать некоторые операции атомарными? Я не хочу использовать для этого тяжелую артиллерию, а также не хотел бы использовать какие-то внешние ресурсы. Другими словами - то, что выглядит и чувствует себя «правильно».

Мне нравится решение, представленное в приведенной выше ссылке, потому что именно это я и делаю - обмениваюсь ссылками. И если я правильно понимаю, я бы охранял только эти 2 строки, и другой код не должен быть изменен! Но я буду ждать окончательного ответа.

Фон

Потому что на каждый N-й вопрос вместо ответа я читаю «но зачем вы используете ...» здесь: Как скопировать итератор в Scala? : -)

Мне нужно скопировать итератор (сделать разветвление), и такое решение является наиболее "правильным", о котором я читал. Проблема в том, что он уничтожает оригинальный итератор.

Решения

Замки

Например, здесь: http://www.ibm.com/developerworks/java/library/j-scala02049/index.html

Единственная проблема, которую я вижу здесь, это то, что я должен поставить блокировку на эти две строки, и любое другое использование на iter. Сейчас это мелочь, но когда я добавляю некоторый код, легко забыть добавить дополнительную блокировку.

Я не говорю «нет», но у меня нет опыта, поэтому я хотел бы получить ответ от кого-то, кто знаком со Scala, указать направление - какое решение является лучшим для такой задачи, и в долгосрочной перспективе -run.

Неизменяемый итератор

Хотя я ценю объяснение Paradigmatic, я не понимаю, как такой подход подходит для моей проблемы. Дело в том, что класс IteratorWrapper должен обернуть итератор - то есть необработанный итератор должен быть скрыт внутри класса (обычно это делается путем его приватности). Такие методы, как hasNext () и next () также должны быть упакованы. Обычно next () изменяет состояние объекта (итератора), поэтому в случае неизменяемого IteratorWrapper он должен возвращать как новый IteratorWrapper, так и состояние next () (успешно или нет). Другим решением было бы возвращать NULL, если raw next () не работает, в любом случае это делает использование IteratorWrapper не очень удобным.

Хуже того, до сих пор не существует простого способа скопировать такой IteratorWrapper.

Так что или я что-то упускаю, или на самом деле классический подход к созданию атомарного куска кода чище. Поскольку вся нагрузка содержится внутри класса, и пользователю не нужно платить цену за то, как IteratorWrapper обрабатывает данные (в данном случае необработанный итератор).

1 Ответ

4 голосов
/ 19 октября 2011

Подход Scala заключается в поддержке неизменяемости, когда это возможно (и очень часто возможно).Тогда вам больше не нужно копировать конструкторы, блокировки, мьютекс и т. Д.

Например, вы можете преобразовать итератор в List при построении объекта.Поскольку списки являются неизменяемыми, вы можете безопасно делиться ими без блокировки:

class IteratorWrapper[A]( iter: Iterator[A] ) {
  val list = iter.toList

  def iteratorCopy = list.iterator
}

Здесь IteratorWrapper также является неизменным.Вы можете безопасно передать его.Но если вам действительно нужно изменить упакованный итератор, вам понадобятся более требовательные подходы.Например, вы могли бы:

  1. Использовать блокировки
  2. Превратить оболочку в Actor
  3. Использовать STM (akka или другие реализации).

Пояснения: Мне не хватает информации о ваших проблемных ограничениях.Но вот как я это понимаю.

Несколько потоков должны проходить одновременно Iterator.Возможный подход - скопировать его перед передачей ссылки на потоки.Тем не менее, практика Scala направлена ​​на совместное использование неизменных объектов, которые не нужно копировать.

С помощью стратегии копирования вы могли бы написать что-то вроде:

//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 не пересекаетсписок.Так что это никак не уменьшит производительность (в отличие от реального копирования итератора каждый раз).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...