golang で Future を実現する
ここで言う Future オブジェクトとは、clojure の Future を使用して作成できるオブジェクトのようなものことをいう(結果をキャッシュしてないとかはあるけど、その辺は割愛している)。 つまり、何か処理を受け取りその処理を別 thread で実行するオブジェクトのことで、それを golang で作る。
シンプルな Future
とにかくブロックせずに、後から値を取り出せるようなオブジェクトを作成する場合は以下のようにする。
ジェネリクスがないので int に対する Future を書いた。NewFuture
の引数の f
がブロックするような重い処理をする。
クロージャ f
の戻り値を future (chan int)
にいれて、Get()
を使ってあとからその値を取っている。
type Future chan int func (f *Future) Get() int { return <-*f } func NewFuture(f func() int) *Future { c := make(Future) go func() { c <- f() }() return &c }
使い方は以下のようになる。
f := NewFuture(func() int { // a time-consuming code return 10 }) f.Get() // 10
Get()のブロックを回避
上の実装だと、Get()
したタイミングで NewFuture
に渡した処理が終わっていなかった場合そこでブロックしてしまう。
そこで、処理中 (pendding
) か終了 (filfilled
) かをブロックせずにわかるようなメソッドを持った Future オブジェクトは以下のようになる。
const ( Pending = iota Fulfilled = iota ) type Future struct { State int ch chan int } func (f *Future) Get() int { return <-f.ch } func NewFuture(fn func() int) *Future { f := Future{ State: Pending, ch: make(chan int), } go func() { ret := fn() f.State = Fulfilled f.ch <- ret }() return &f }
State
を使用すると渡され処理が完了しているかわかるので例えば、以下のように終了したか(fulfilled
)かどうかによって処理を変えられる。
f := NewFuture(func() int { // a time-consuming code return 10 }) if f.State == Fulfilled { f.Get() // 10 } else { // something code f.Get() // 10 }
感想
以上のように golang を使うと Future はシンプルに書けるので、無理に Future ライブラリを使うのではなくてその都度かんたんなものを作れば良さそうな気がした。