Multithreading `foreach `过度并行化的集合永远不会启动

Multithreading `foreach `过度并行化的集合永远不会启动,multithreading,mongodb,scala,collections,parallel-processing,Multithreading,Mongodb,Scala,Collections,Parallel Processing,我有一个Mongo数据库,里面有我想并行处理的作业;我想尝试使用并行集合来透明地处理线程(并不是说使用线程池会非常困难)。我想出了这个密码: def run(stopSignal: SynchronizedQueue[Any]) = { val queue = new Iterator[Job] { private var prevId = new ObjectId("000000000000000000000000") def hasNext = stopSignal.i

我有一个Mongo数据库,里面有我想并行处理的作业;我想尝试使用并行集合来透明地处理线程(并不是说使用线程池会非常困难)。我想出了这个密码:

def run(stopSignal: SynchronizedQueue[Any]) = {
  val queue = new Iterator[Job] {
    private var prevId = new ObjectId("000000000000000000000000")

    def hasNext = stopSignal.isEmpty

    @tailrec
    def next = {
      val job = Job
        .where(_.status eqs Pending)
        // this works because the IDs start with a timestamp part
        .where(_._id gt prevId)
        .orderAsc(_.regTime)
        .get()
      job match {
        case Some(job) =>
          prevId = job.id
          println(s"next() => ${job.id}")
          job
        case None if hasNext =>
          Thread.sleep(500) // TODO: use a tailable cursor instead
          next
        case None =>
          throw new InterruptedException
      }
    }
  }

  try {
    queue.toStream.par.foreach { job =>
      println(s"processing ${job.id}...")
      processOne(job)
      println(s"processing complete: ${job.id}")
    }
  } catch { case _: InterruptedException => }
}
这将产生:

next()=>53335f7bef867e6f0805abdb
next()=>53335fc6ef867e6f0805abe2
next()=>53335ffcef867e6f0805abe6
next()=>53336005ef867e6f0805abe7
next()=>53336008ef867e6f0805abe8
next()=>5333600cef867e6f0805abe9
但这一过程从未开始;i、 e.传递给
foreach
的函数永远不会被调用。如果我删除
.par
调用,它可以正常工作(当然是连续工作)


到底是哪个抽象在这里泄漏?我如何解决这个问题?或者我应该为此放弃使用并行集合,转而使用更简单的线程池方法吗?

par方法将首先将流的元素排入
ParSeq
中。 因此,当您调用
queue.toStream.par
时。它将遍历流(调用底层迭代器的
hasNext
和next方法,直到迭代器没有next为止)。在检索到所有作业之后,它开始调用
processJob

比如说

scala> (1 to 100).iterator.toStream
res7: scala.collection.immutable.Stream[Int] = Stream(1, ?)

scala> (1 to 100).iterator.toStream.par
res8: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)
par方法不是惰性的

如果您只是希望并行执行(实际上它已经并行了,但不是懒惰的):

  • 将流分组为批。调用每个批次的
    par
  • 您可以将
    processJob
    方法放在
    未来的
    
    或您可以考虑使用<代码>演员< /C>模式来处理这些事情
  • 使用

最终选择了线程池方法;我仍然保留@jilen的答案作为被接受的答案,因为他回答了我的问题,但我也发布了解决方案:

从(自身取自)获取
BlockingExecutor
片段(转换为Scala),然后直接绕过任何Scala/Future包装使用它:

// 2 processing; 2 in the queue; 4 total
val executor = new BlockingExecutor(poolSize = 2, queueSize = 2)

try {
  queue.foreach { job =>
    executor.submit(new Runnable {
      def run = {
        println(s"processing ${job.id}...")
        processOne(job)
        println(s"processing complete: ${job.id}")
      }
    })
  }
} catch { case _: InterruptedException => }
executor.shutdown

我希望免费获得懒惰的并行处理;我想这终究不会那么容易:)我不想让演员做这么简单的工作(而且我不喜欢Akka的动态类型演员,反正类型化频道似乎还没有发布);我将看看是否可以将
Future
和一个适当大小的线程池结合起来,在一个简单的
for
循环中实现我所需要的。@jiljen:我能在哪里找到
背后的理由吗?在我看来,
.par
(和
迭代器
上懒惰是有道理的,如果它首先在它上面定义的话)。@ErikAllik我认为
并行
集合与普通集合非常相似,只是它的操作是并行的
.par
方法创建一个
ParVector
,它是一个不可变的集合,并且必须事先知道它的元素。我仔细考虑后发现,您可以将流分组成块。除了调用PAR方法。在我的情况下,组块不会起作用,但是无论如何…我只是想知道为什么并行集合必须仅限于
ParVector
;如果你不知道,那没关系。@ErikAllik抱歉,你可以在scala用户组中提问。