golang で Future を実現する

ここで言う Future オブジェクトとは、clojureFuture を使用して作成できるオブジェクトのようなものことをいう(結果をキャッシュしてないとかはあるけど、その辺は割愛している)。 つまり、何か処理を受け取りその処理を別 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 ライブラリを使うのではなくてその都度かんたんなものを作れば良さそうな気がした。