无法使用功能性API执行并行计算

时间:2019-04-28 19:45:14

标签: scala functional-programming

我正在尝试实现关于并行性一章的红皮书中的countWords函数。当我将线程池传递给函数并修改函数以打印计数单词的线程时,我只能看到打印的主线程。这表明我无法使该函数并行执行。

我目前拥有的东西:

type Par[A] = ExecutorService => Future[A]

def asyncF[A, B](f: A => B): A => Par[B] = a => lazyUnit(f(a))

def lazyUnit[A](a: => A): Par[A] = fork(unit(a))

def unit[A](a: A): Par[A] = (_: ExecutorService) => UnitFuture(a)

def fork[A](a: => Par[A]): Par[A] = 
es => es.submit(new Callable[A] {
  def call = a(es).get
})

def countWords(l: List[String]): Par[Int] = map(sequence(l.map(asyncF {
println(Thread.currentThread())
s => s.split(" ").length
})))(_.sum)

我跑步时:

val listPar = List("ab cd", "hg ks", "lh ks", "lh hs")

val es = Executors.newFixedThreadPool(4)

val counts = countWords(listPar)(es)

println(counts.get(100, SECONDS))

我得到:

Thread[main,5,main]
8

我希望看到列表的每个元素都打印有一个线程(因为有四个元素和一个大小为4的线程池),但是我只能看到主线程被打印。

有什么建议吗? 谢谢

1 个答案:

答案 0 :(得分:1)

在提出问题时,我想从一条建议开始-您应该始终提供MCVE。您的代码无法编译;例如,我不知道UnitFuture的来源,也不知道您使用的sequence的实现是什么。

这是与标准Scala一起使用的代码段。首先,说明:

方法countWords包含一个要计数的字符串列表,以及两个服务-一个用于在不同线程上处理Java Futures,另一个用于在不同线程上处理Scala Futures。 Scala one是通过ExecutionContext.fromExecutor方法从Java one派生的。

为什么同时使用Java和Scala?好吧,我想保留Java,因为那是您最初编写代码的方式,但是我不知道如何sequence Java Future。所以我所做的是:

  • 对于每个子字符串:
    • 派生一个Java Future任务
    • 将其变成Scala未来
  • 对获得的Scala期货列表进行排序

如果您不熟悉隐式,则可以(如果您打算使用Scala的话)。在这里,我隐式使用了执行上下文,因为它删除了很多样板-这样,在转换为Scala future时,映射/排序等过程中,我不必显式传递它。

现在代码本身:

import java.util.concurrent.{Callable, ExecutorService, Executors}
import java.util.concurrent.{Future => JFuture}

import scala.concurrent.{ExecutionContext, Future}

def scalaFromJavaFuture[A](
  javaFuture: JFuture[A]
)(implicit ec: ExecutionContext): Future[A] =
  Future { javaFuture.get }(ec)

def fork(s: String)(es: ExecutorService): java.util.concurrent.Future[Int] =
  es.submit(new Callable[Int] {
    def call = {
      println(s"Thread: ${Thread.currentThread()}, processing string: $s")
      s.split(" ").size
    }
  })

def countWords(l: List[String])(es: ExecutorService)(implicit ec: ExecutionContext): Future[Int] = {
  val listOfFutures = l.map(elem => scalaFromJavaFuture(fork(elem)(es)))
  Future.sequence(listOfFutures).map(_.sum)
}

val listPar = List("ab cd", "hg ks", "lh ks", "lh hs")

val es = Executors.newFixedThreadPool(4)
implicit val ec = ExecutionContext.fromExecutor(es)

val counts = countWords(listPar)(es)

counts.onComplete(println)

示例输出:

  

线程:线程[pool-1-thread-1,5,main],正在处理字符串:ab cd
  线程:Thread [pool-1-thread-3,5,main],处理字符串:hg ks
  线程:线程[pool-1-thread-2,5,main],处理字符串:lh ks
  线程:Thread [pool-1-thread-4,5,main],处理字符串:lh hs
  成功(8)

请注意,取决于执行上下文来确定线程。运行几次,您将自己看到-您可能会遇到例如仅使用了两个线程:

  

线程:线程[pool-1-thread-1,5,main],正在处理字符串:ab cd
  线程:Thread [pool-1-thread-3,5,main],处理字符串:hg ks
  线程:Thread [pool-1-thread-1,5,main],处理字符串:lh ks
  线程:Thread [pool-1-thread-1,5,main],处理字符串:lh hs
  成功(8)