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)
}
}
)