使函数在scala中的多个线程上运行

时间:2015-07-13 14:03:25

标签: multithreading scala

我有以下功能可以解决迷宫问题:

def getWayDepthFirst(maze: Maze, position: Position, way: List[Position]): List[List[Position]] = {
if (!canWalkOnCell(maze, position)) {
  Nil

} else {

  if (isExit(position, maze)) {
    List(position :: way)
  } else {
    val explorationArea: List[Position] = List(position.north, position.east, position.south, position.west) filter (x => !way.contains(x) && canWalkOnCell(maze, x))
    explorationArea.flatMap {
      (
        notYetVisitedPosition =>
          getWayDepthFirst(maze, notYetVisitedPosition, position :: way))
    }
  }
}

}

据我所知,我将不得不在另一个线程上启动递归调用,因为每个递归步骤都将沿着迷宫的路径前进。

我怎样才能在Scala中执行此操作?有没有提供此课程的课程?

1 个答案:

答案 0 :(得分:-3)

这是解决方案

import scala.concurrent._ ; import ExecutionContext.Implicits.global; import scala.concurrent.duration._
import scala.async.Async.{async, await}

def depthFirst(pos: Pos = Pos(0,0), way: List[Pos] = Nil): Future[List[List[Pos]]] = async {
    if (!available(pos)) {
      Nil

    } else {

      if (isExit(pos)) {
        List(pos :: way)
      } else {
        val explorationArea: List[Pos] = Directions map (pos) filter (x => !way.contains(x) && available(x))
        val listOfFutures = explorationArea. map {
            notYetVisitedPosition => trace(depthFirst _, notYetVisitedPosition, pos :: way)
        }
        await{Future.sequence(listOfFutures) map {_.flatten}} 
      }
    }
}

val solutions: Seq[List[Pos]] = Await.result(depthFirst(), Duration.Inf);

以下是我测试它的方式

object maze extends App {

    def available(pos: Pos) = pos match {case Pos(x,y) => x >= 0 && y >= 0 && x < 4 && y < 4 }
    def isExit(pos: Pos) = {Thread.sleep(20) ; pos == Pos(1,1)}

    case class Pos(val x: Int, val y: Int) {

        def inc(axis: Boolean, delta: Int) = axis match {
            case  true => Pos(x + delta, y)
            case false => Pos(x, y + delta)
        }
    }

    object Directions {

        val ops = for {
            axis <- List(true, false)
            delta <- List(1, -1)
        } yield (axis, delta)

        def map(pos: Pos) = ops.map {case (axis, delta) => pos.inc(axis, delta)}

    }

    val time1 = System.currentTimeMillis()

    def trace[T](body: (Pos, List[Pos]) => T, pos: Pos = Pos(0,0), way: List[Pos] = Nil): T = {
        val path = (pos :: way).reverse.map {case Pos(x,y) => x + ":" + y} mkString ","
        //println (path)
        def time = System.currentTimeMillis() - time1
        val commands = //"th " + Thread.currentThread().getName + ": " +
            //path + " <= " +
            (way.foldLeft (("", pos)){case ((path, cur), prev) => //println(cur + " - " + prev)
            val command = (cur.x - prev.x, cur.y - prev.y) match {
                case (-1, _) => "l"
                case (1, _) => "r"
                case (_, -1) => "d"
                case (_, 1) => "u"
            }
            (path + command, prev)
        }._1.reverse)
        //println(commands + " in at " + time)
        val result = body(pos, way)
        //println(commands + " out at " + time)
        result
    }


    def serial: List[List[Pos]] = {
        def depthFirst(pos: Pos = Pos(0,0), way: List[Pos] = Nil): List[List[Pos]] = {
            if (!available(pos)) {
              Nil

            } else {

              if (isExit(pos)) {
                List(pos :: way)
              } else {
                val explorationArea: List[Pos] = Directions map (pos) filter (x => !way.contains(x) && available(x))
                explorationArea.flatMap {
                  (
                    notYetVisitedPosition =>
                      trace(depthFirst _, notYetVisitedPosition, pos :: way))
                }
              }
            }
        }

        depthFirst()
    }


    def futures: List[List[Pos]] = {
        import scala.concurrent._ ; import ExecutionContext.Implicits.global; import scala.concurrent.duration._

        def depthFirst(pos: Pos = Pos(0,0), way: List[Pos] = Nil): Future[List[List[Pos]]] = {
            if (!available(pos)) {
              Future{Nil}

            } else {

              if (isExit(pos)) {
                Future {List(pos :: way)}
              } else {
                val explorationArea: List[Pos] = Directions map (pos) filter (x => !way.contains(x) && available(x))
                val listOfFutures = explorationArea. map {
                    notYetVisitedPosition => trace(depthFirst _, notYetVisitedPosition, pos :: way)
                }
                //listOfFutures.foldLeft(Future{List[List[Pos]]()}){case (acc, fllp) => acc flatMap {acc => fllp map {llp => llp ++ acc}}}
                Future.sequence(listOfFutures) map {_.flatten} // this collection has the same effect as above
              }
            }
        }

        Await.result(depthFirst(), Duration.Inf);
    }

    def asyncAwait: Seq[List[Pos]] = {
        import scala.concurrent._ ; import ExecutionContext.Implicits.global; import scala.concurrent.duration._
        import scala.async.Async.{async, await}

        def depthFirst(pos: Pos = Pos(0,0), way: List[Pos] = Nil): Future[List[List[Pos]]] = async {
            if (!available(pos)) {
              Nil

            } else {

              if (isExit(pos)) {
                List(pos :: way)
              } else {
                val explorationArea: List[Pos] = Directions map (pos) filter (x => !way.contains(x) && available(x))
                val listOfFutures = explorationArea. map {
                    notYetVisitedPosition => trace(depthFirst _, notYetVisitedPosition, pos :: way)
                }
                await{Future.sequence(listOfFutures) map {_.flatten}} 
              }
            }
        }

        Await.result(depthFirst(), Duration.Inf);
    }

    def streams: Seq[List[Pos]] = {
        import scala.concurrent._ ; import ExecutionContext.Implicits.global; import scala.concurrent.duration._

        def depthFirst(pos: Pos = Pos(0,0), way: List[Pos] = Nil): Stream[List[Pos]] = {
            if (!available(pos)) {
              Stream{Nil}

            } else {

              if (isExit(pos)) {
                Stream(pos :: way)
              } else {
                val explorationArea = Directions map (pos) filter (x => !way.contains(x) && available(x))
                explorationArea.toStream flatMap {
                    notYetVisitedPosition => depthFirst(notYetVisitedPosition, pos :: way)
                }
              }
            }
        }

        val future = Future.traverse(depthFirst())(x => Future{x})
        Await.result(future, Duration.Inf);
    }


    List(
        "serial" -> serial _,
        //"streams" -> streams _,
        "fut" -> futures _,
        "await" -> asyncAwait _,

        "stub" -> {() => List()} // stub handles comma when you comment the items above
    ) foreach { case (title, func) =>
        val time1 = System.currentTimeMillis()
        val result = func()
        val time = title + " produced " + result.length + " paths in " + (System.currentTimeMillis() - time1) + " ms"
        println(time)
        //result map {_.reverse} foreach println ; println(time)
    }


}

带输出

serial produced 114 paths in 10382 ms
fut produced 114 paths in 10432 ms
await produced 114 paths in 1381 ms <- 7.5 times faster

如果我们删除isExit中的人为延迟,您会看到期货版本不比串行执行快(并且速度慢4倍)。你不应该听取上面的专家的意见,因为他们说保护期货

  1. 您应该阅读有关期货的信息。 - 你做到了。它有什么用?我预计,期货的解决方案会变慢。而且,相反指出问题,他们提出了
  2. 微基准测试,显然与测试结果无关。测试时间显而易见地说我们有一个单线程执行期货。指出他是isExit方法的延迟(20毫秒延迟,模仿一个相当严重的负载)和代码运行超过10秒,如果他们不相信。问问他们,我的评估应该花多少小时来洗掉你的脏uBM标签?假设Futured解决方案在同一个JVM中的串行之后运行(但速度慢了4倍)。如何进行优化呢?
  3. 捍卫未来,他们解释的话反之亦然。他们将futures will be executed one-after-another, likewise there is no parallelism解释为unlimited parallelism.
  4. 他们说granularity is too fine in this assignment, so that you do not get any benefit。这是完全专业化的另一个标志。显然,持续很长时间并以指数方式爆炸的任务,就像这个任务一样,当然要在平行线上进行探索,特别是在根本上。问他们大型勘探区域有多大可能,例如4个独立的大问题分支has too fine granularity
  5. 我想说的是,这是一个非常好的问题,因为它是一个适度的问题,也表明蛮力use of Futures不起作用。您不能将函数调用包装到future中并获得并行执行。我的代码的问题不是微基准测试或问题的粒度。转换Future [解决方案列表]很困难,当你的depthFirst = Future {... subsolutions = future of lists}时,这是必要的。您可以将期货列表展平为Future [solutionsList],但您需要从未来提取solutionsList。回顾Eric Mayer的FRP讲座,我决定需要一些等待并转向异步等待解决方案。我的期货解决方案实际上是在主线程中执行的,因为为了解决问题,我在一个线程中做了所有事情并将结果包装到了未来,这是愚蠢的事情,但是专家们没有认识到它并且决定责怪缩影测试。当人们说白色是彻头彻尾的黑色时,不要相信。