Software Engineer Blog

エンジニアブログです。技術情報(Go/TypeScript/k8s)や趣味の話も書くかもです。

sync.Poolの挙動について(Golang)

概要

標準パッケージのsync.Poolを利用して、ファイルの中身を常にメモリ上(=キャッシュ)におきたかったのですが、動作が不定のためうまく利用できなかった話です。

Go version

% go version
go version go1.8.1 darwin/amd64

sync.Pool概要

概要としては、以下の認識です

  • オブジェクトを一時的に、保持するための機能
  • 再利用されるオブジェクトをキャッシュする利用法
  • 複数のgoroutineで安全に利用可能
  • copy禁止(noCopystructが埋め込まれている)

メソッド

Get

func (p *Pool) Get() interface{}

  • Poolよりitemを取り出す
  • 取り出されたitemはPoolから削除
  • Putで入れた値とGetで取り出す値の関係を考慮すべきでない

Put

func (p *Pool) Put(x interface{})

  • Poolに値を追加する

動作確認

下記のコードを利用して、Getした際とPutした際の挙動を確認してみました。
https://github.com/midorigreen/gopool/blob/master/main.go

package main

import (
    "fmt"
    "net/http"
    "sync"
)

var pool = sync.Pool{
    New: func() interface{} {
        return "init"
    },
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/pool", poolHandler)
    mux.HandleFunc("/pool/change", poolChangeHandler)
    http.ListenAndServe(":8888", mux)
}

func poolHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("----------------------------------")
    fmt.Println("/pool")
    fmt.Println(get().(string))
    fmt.Println("----------------------------------")
}

func poolChangeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("----------------------------------")
    s := get().(string)
    fmt.Println("/pool/change")
    pool.Put(s)
    fmt.Println("-- changed --")
    fmt.Println("----------------------------------")
}

func get() interface{} {
    s := pool.Get().(string)
    cpS := s
  // Getした値を再度Poolに戻している
    pool.Put(s)
    return cpS
}

出力例

実際の出力例です。
上記コードより、get()で呼び出した際は、Get→Putとしているため
同じ値がGet()で取得されて欲しいところです。
ですが、一度/pool/changeAPIを叩いた後も"init"が出力されていることから、
Get→Putで値を戻したところで、必ず同じ値をGet()してくれるわけではなさそうです。

----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool/change
-- changed --
----------------------------------
# ↓ これ以降はすべて "change handler" が出力されていて欲しい
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------

感想

Callers should not assume any relation between values passed to Put and the values returned by Get.
→ Putで入れた値とGetで取り出す値の関係を考慮すべきでない

とあるように、Put()で入れたからGet()で必ず入れた値が取得できるわけではないことを念頭に置いて開発しないといけないようです。
ファイルの中身をキャッシュしておいて、書き換え時は更新等を行いたかったのですが、 更新をしたデータをPut()で突っ込んでも返ってくるとは限らなくなってます。
結論、あまり良い使い方ではなさそうです。(一意にキャッシュを保てないので、実データと差分がでてしまうため)

補足

@nobonoboさんにも指摘頂きました。

参考