Проблема с использованием актеров Скала - PullRequest
0 голосов
/ 17 ноября 2011

Я прочитал, что при использовании реакции все актеры могут выполняться в одном потоке.Я часто обрабатываю коллекцию параллельно, и мне нужно вывести результат.Я не верю, что System.out.println является потокобезопасным, поэтому мне нужна защита.Одним способом (традиционным способом) я мог бы сделать это:

val lock = new Object
def printer(msg: Any) {
  lock.synchronized {
    println(msg)
  }
}

(1 until 1000).par.foreach { i =>
  printer(i)
}

println("done.")

Как это первое решение сравнивается с использованием действующих лиц с точки зрения эффективности?Правда ли, что я не создаю новый поток?

val printer = actor {
  loop {
    react {
      case msg => println(msg)
    }
  }
}

(1 until 10000).par.foreach { i =>
  printer ! i
}

println("done.")

Однако это не очень хорошая альтернатива, потому что код актера никогда не завершается.Если я помещу println внизу, он никогда не попадет, даже если он выглядит так, как будто он проходит каждую итерацию для i.Что я делаю не так?

Ответы [ 4 ]

1 голос
/ 17 ноября 2011

Один экземпляр субъекта никогда не обработает более одного сообщения одновременно. Независимо от того, какой пул потоков выделен для актеров, каждый экземпляр актера будет одновременно занимать только один поток, поэтому вам гарантируется, что вся печать будет обрабатываться последовательно.

Что касается незавершенности, исполнение актера никогда не возвращается из react или loop, поэтому:

val printer = actor {
  loop {
    react {
      case msg => println(msg)
    }
    // This line is never reached because of react
  }
  // This line is never reached because of loop
}

Если вы замените loop и react на цикл while и receive, вы увидите, что все внутри цикла while выполняется должным образом.

1 голос
/ 17 ноября 2011

Теперь, когда у вас есть код актера, у вас есть только один актер, выполняющий всю печать.Как видно из выполнения кода, все значения выводятся последовательно Actor, тогда как в коде параллельной коллекции они не в порядке.Я не слишком знаком с параллельными коллекциями, поэтому я не знаю увеличения производительности между ними.

Однако, если ваш код выполняет много работы параллельно, вы, вероятно, захотите пойти снесколько актеров.Вы можете сделать что-то вроде этого:

def printer = actor {
  loop {
    react {
      case msg => println(msg)
    }
  }
}

val num_workers = 10
val worker_bees = Vector.fill(num_workers)(printer)

(1 until 1000).foreach { i =>
    worker_bees(i % num_workers) ! i
}

Определение важно.Таким образом, вы на самом деле создаете несколько актеров, а не просто затопляете одного.

1 голос
/ 17 ноября 2011

Чтобы исправить вашу реализацию актера, вы должны указать актеру выйти до того, как программа также выйдет.

val printer = actor {
  loop {
    react {
      case "stop" => exit()
      case msg => println(msg)
    }
  }
}

(1 until 1000).par.foreach { printer ! _ }

printer ! "stop"

В обоих ваших примерах есть пулы потоков, включающие поддержку как библиотеки параллелей, так и библиотеки акторов, но они создаются по мере необходимости.

Однако println является поточно-ориентированным, поскольку у него действительно есть блокировка во внутренних органах.

(1 until 1000).par.foreach { println(_) } // is threadsafe

Что касается производительности, есть много факторов. Во-первых, переход от блокировки, за которую борются несколько потоков, к блокировке, используемой только одним потоком (одним действующим лицом), повысит производительность. Во-вторых, если вы собираетесь использовать актеров и хотите производительность, используйте Акка. Актеры Akka невероятно быстрые по сравнению с актерами scala. Кроме того, я надеюсь, что стандартный вывод, в который println пишет, идет в файл, а не на экран, поскольку использование драйверов дисплея может снизить производительность.

Использование библиотеки Parallels прекрасно для производительности, так как вы можете использовать несколько ядер для своих вычислений. Если каждое вычисление очень мало, попробуйте маршрут актера для централизованной отчетности. Однако, если каждое вычисление является значительным и занимает приличное количество процессорного времени, то придерживайтесь только самого println. Вы действительно не находитесь в ситуации блокировки.

0 голосов
/ 17 ноября 2011

Я не уверен, что могу правильно понять вашу проблему.Для меня ваш код актера работает нормально и заканчивается.

Тем не менее, вы можете безопасно использовать println для параллельных коллекций, так что все, что вам действительно нужно, это что-то вроде этого:

(1 until 1000).par.foreach { println(_) }

Работает какочарование здесь.Я предполагаю, что вы уже знаете, что порядок вывода будет отличаться, но я просто хочу подчеркнуть это еще раз, потому что вопрос возникает очень часто.Поэтому не ожидайте, что цифры будут последовательно прокручиваться вниз по экрану.

...