在多线程环境中包含可变变量的隐式类

时间:2019-01-25 19:32:28

标签: multithreading scala implicit mutability

我需要实现一个parallel方法,该方法需要两个计算块ab,并在一个新线程中启动每个计算块。该方法必须返回一个包含两个计算结果的元组。它应该具有以下签名:

def parallel[A, B](a: => A, b: => B): (A, B)

我设法通过使用类似Java的直接方法解决了该练习。然后,我决定用隐式类组成一个解决方案。就是这样:

object ParallelApp extends App {

  implicit class ParallelOps[A](a: => A) {
    var result: A = _

    def spawn(): Unit = {

      val thread = new Thread {
        override def run(): Unit = {
          result = a
        }
      }
      thread.start()
      thread.join()
    }
  }

  def parallel[A, B](a: => A, b: => B): (A, B) = {
    a.spawn()
    b.spawn()
    (a.result, b.result)

  }

  println(parallel(1 + 2, "a" + "b"))

}

由于未知原因,我收到输出(null,null)。您能指出问题出在哪里吗?

2 个答案:

答案 0 :(得分:3)

剧透警报:这并不复杂。这很有趣,就像是魔术一样(如果您考虑阅读有关Java Memory Model“ funny”的文档,那就是)。如果您还没有弄清楚,我强烈建议您尝试解决,否则它不会很有趣。有人应该从中得出一个“零除证明2 = 4”


考虑以下简短示例:

implicit class Foo[A](a: A) {
  var result: String = "not initialized"
  def computeResult(): Unit = result = "Yay, result!"
}

val a = "a string"
a.computeResult()

println(a.result)

运行时,它会打印

not initialized

尽管事实是我们调用了computeResult()并将result设置为"Yay, result!"。问题是两个调用a.computeResult()a.result属于Foo的两个完全独立的实例。隐式转换执行了两次,第二个隐式创建的对象对第一个隐式创建的对象的更改一无所知。它与线程或JMM完全无关。

顺便说一句:您的代码不是并行的。在调用join之后立即调用start并不会给您带来任何好处,您的主线程只会变得空闲,并等待另一个线程结束。任何时候都不会有两个线程同时执行任何有用的工作。

答案 1 :(得分:0)

编辑:修复了安德烈·图金(Andrey Tyukin)指出的错误

解决问题的一种方法是使用Scala Futures

DocumentationTutorialUseful Klang Blog

通常需要这些库的组合:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
import scala.concurrent.duration._

一个异步示例:

def parallelAsync[A,B](a: => A, b: => B): Future[(A,B)] = {
  // as per Andrey Tyukin's comments, this line runs
  // the two futures sequentially and we do not get
  // any benefit from it.  I will leave this line here
  // so others will not fall in my trap
  //for {i <- Future(a); j <- Future(b) } yield (i,j)
  Future(a) zip Future(b)
}

parallelAsync(1 + 2, "a" + "b").onComplete {
  case Success(x) => println(x)
  case Failure(e) => e.printStackTrace()
}

如果您必须在全部完成之前都阻止,则可以使用以下方法:

def parallelSync[A,B](a: => A, b: => B): (A,B) = {
  // see comment above
  //val f = for { i <- Future(a); j <- Future(b) } yield (i,j)
  val tuple = Future(a) zip Future(b)
  Await.result(tuple, 5 second)
}

println(parallelSync(3 + 4, "c" + "d"))

在运行这些小示例时,不要忘了最后睡一会儿,这样程序就不会在结果返回之前结束

Thread.sleep(3000)