Ken Wakita (https://prg1-2019.github.io/lecture/web/)
2019.11.8
Future オブジェクト:どこかで計算を実行し,いずれ計算が終わった暁には,その答えをくれるオブジェクト.
計算結果の型が T のとき,Future オブジェクトの型は Future[T]
f() を実行する.
g() を計算する Future オブジェクトを生成する.
計算資源に余裕がある場合は(つまり,暇なプロセッサがあるとき)Future オブジェクトはすぐに g() の計算を開始する.
(Future オブジェクトの計算を待たずに)h() の計算を始める.
計算資源に余裕があり,g()とh()の計算が重い場合はこれらの二つの計算は並列実行される.
Await.result(<Future>, <時間>) によって、必要なら <Future> の計算を最大 <時間> だけ待ち、<Future>の計算結果を取得する。指定した時間内に計算が終わらない場合は TimeoutException 例外を発生する。
sbt:lx11> runMain Fib future
[info] Running (fork) Fib future
[info] Hello + ...: Hello future!
[success] Total time: 1 s
Future f は文字列のリストを連結する.
println メソッド中の Await.result(f, 最大待ち時間) が Future との同期と値の授受を実施する.
Await.result(f, ...): f の計算を待つ
Await.result(..., Duration.Inf) 永遠に待ち続ける
Await.result(...) の結果は f の計算結果
ふたつの Future (f1 と f2) の並列実行を考える.
最終的には f1 と f2 の計算結果を用いて計算したい.たとえば,分割統治法で大きな問題を複数の部分問題に分割したときに,それぞれの部分問題を Future として独立に並列計算し,それらの結果を合成して最終結果を得たい.
上の例は AND 並列
OR 並列はどうする?
f1, f2 が Future のとき for { v1 <- f1; v2 <- f2 } yield ... によって,複数の Future との同期と値の受理を記述できる.
f1, f2, … の計算がすべて完了し、v1, v2, … がすべて揃ったら yield ... によって,それらの結果をまとめあげた計算をができる。
sbt:lx11> runMain Fib add
[info] Running (fork) Fib add
[info] 1 + 2 = 3
[success] Total time: 1 s
f1:Future[T], f2:Future[U] のとき,f1.zip(f2) は Future[(T, U)] を返す.
ふたつの Future を合成し、それぞれの計算結果(それぞれの型は T, U)の組(型は (T, U))を計算するFutureを返す.
def add_zip() {
val f1: Future[Int] = Future { 1 }
val f2: Future[Int] = Future { 2 }
val (v1, v2) = Await.result(f1.zip(f2), Duration.Inf)
println(f"1 + 2 = ${v1 + v2}")
}Await.result(f1.zip(f2), ...) は zip で合成した Future の計算を待って,f1, f2 の結果の組を得る。
zip で合成したFutureの実行例sbt:lx11> runMain Fib add_zip
[info] Running (fork) Fib add_zip
[info] 1 + 2 = 3
[success] Total time: 1 s
Promise[T] は Future[T] 型の Future オブジェクトの計算結果を保持するオブジェクト.
Future にくっついている覗き穴のようなもの
Future が未了なら Promise の値は未了 (p.isComplete == false)
Future が正常終了してたら Promise の値は Success[T]
Future が以上終了してたら Promise の値は Failure[T]
Promise p が割り当てられている Future は p.future
Future が Promise に値を通知する方法は p.success(計算結果)
Scala では o.f(x) を o f x と略記できるので、上の式を p success 計算結果 と書くことが多い。
def promise(): Unit = {
val s = "Hello"
val p = Promise[String]()
Future { p success List(s, " future!").reduce((s1, s2) => s1 + s2) }
println(f"Value from promise: ${Await.result(p.future, Duration.Inf)}")
}Promise p を作成
Future を作成し,計算 (List(...).reduce(...)) の結果を p success ... でPromiseに通知
def promise(): Unit = {
val s = "Hello"
val p = Promise[String]()
Future { p success List(s, " future!").reduce((s1, s2) => s1 + s2) }
println(f"Value from promise: ${Await.result(p.future, Duration.Inf)}")
}Await.result(p.future, ...) により,Promise p に結びついた Future と同期
一見、無駄に Promise を使っているように見えるが,Future に一体化された計算と通信を分離することで柔軟性を増している.
sbt:lx11> runMain Fib promise
[info] Running (fork) Fib promise
[info] Value from promise: Hello future!
[success] Total time: 1 s
sbt:lx11> runMain Fib add_promise
[info] Running (fork) Fib add_promise
[info] 1 + 2 = 3
[success] Total time: 1 s
object recursive extends Fibonacci {
def fib(n: Int): Int = {
if (n <= 1) 1
else fib(n-2) + fib(n-1)
}
}n <= 1 ならば 1 を返す.
そうでなければ,
fib(n-2) を計算するfib(n-2) の計算結果を受け取り,覚えておく(v1).fib(n-1) を計算するfib(n-1) の計算結果を受け取り,覚えておく(v2).v1 と v2 の和を計算する.(sum)sum を返す.n <= 1 ならば 1 を返す.(誰に?)
そうでなければ,
fib(n - 2) を計算する
fib(n - 2) の計算結果を受け取り,覚えておく.(v1)
fib(n - 1) を計算する
fib(n - 1) の計算結果を受け取り,覚えておく.(v2)
v1 と v2 の和を計算し,覚えておく.(sum)
sum を返す.(誰に?)
誰に →「計算結果を渡す相手」を明示
「計算結果を渡す相手」を Promise で表現
def sum(promise1: Promise[Int], promise2: Promise[Int], promise_sum: Promise[Int]) = {
val (v1, v2) = Await.result(promise1.future.zip(promise2.future), Duration.Inf)
promise_sum success (v1 + v2)
}計算の引数を受け取る相手 promise1, promise2 promise1 と promise2 はそれぞれ fib(n-1), fib(n-2) を計算
計算結果を渡す相手は promise_sum Promise たちの計算結果を収集したら promise_sum に合計値を伝える。
板書で図示
(
def fib(n: Int, p: Promise[Int]) {
if (n <= 1) p success 1
else {
// fib(n-1), fib(n-2)を計算するFutureとPromise
val promise1 = Promise[Int]() // fib(n-1) の結果を納める Promise
val promise2 = Promise[Int]() // fib(n-2) の結果を納める Promise
Future { fib(n-1, promise1) }
Future { fib(n-2, promise2) }
// fibを計算するFutureたちから結果を受け取りその和をpで待つFutureに伝えるFuture
sum(promise1, promise2, p)
}
}
)