いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-361 (オブジェクトをプーリングして再利用)(sync.Pool)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。よく忘れるのでここにメモメモ。。。

頻繁に利用する構造体や生成にコストが掛かる構造体などをプーリングして再利用しながら処理したい場合がたまにあります。

各言語ごとにプールさせるライブラリは存在していますが、Goの場合は標準ライブラリに sync.Pool というものが存在しています。

使い方自体は、sync.Pool.Newに生成用の関数を予め設定しておき、Getで取得してPutで戻すという流れになります。

以下、自分がたまに忘れるのでここにメモメモ。。。

サンプル

package syncs

import (
    "sync"

    "github.com/devlights/gomy/output"
)

type _PooledObject struct {
    v int
}

func NewPooledObject(v int) *_PooledObject {
    output.Stderrl("[New]", "Call NewPooledObject()")
    return &_PooledObject{v: v}
}

func (me *_PooledObject) Reset() {
    me.v = 0
}

func (me *_PooledObject) Set(v int) {
    me.v = v + 100
}

func (me *_PooledObject) Value() int {
    return me.v
}

// UsePool は、sync.Poolのサンプルです。
//
// # REFERENCES
//   - https://pkg.go.dev/sync@go1.21.4#Pool
func UsePool() error {
    //
    // sync.Pool を利用する際の注意点 (go doc より引用)
    //
    // > Any item stored in the Pool may be removed automatically at any time without notification.
    // > If the Pool holds the only reference when this happens, the item might be deallocated.
    //
    // プールに保持されているオブジェクトは通知無しに自動削除される可能性がある。
    // その際に、プールがそのオブジェクトを参照する唯一であれば、メモリから
    // 開放される可能性がある。
    //
    // > A Pool must not be copied after first use.
    //
    // プールは、最初に使用した後はコピーしてはならない。
    //
    // > The Pool's New function should generally only return pointer types,
    // > since a pointer can be put into the return interface value without an allocation
    //
    // ポインタは割り当てなしで戻りインターフェイス値に入れることができるため、
    // プールの New 関数は通常、ポインタ型のみを返す必要がある.
    //

    const (
        NUM_ITEMS = 20
    )

    var (
        pool = sync.Pool{
            New: func() any {
                return NewPooledObject(0)
            },
        }
        ch   = make(chan int)
        done = make(chan struct{})
        wg   = sync.WaitGroup{}
    )

    wg.Add(NUM_ITEMS)

    for i := 0; i < NUM_ITEMS; i++ {
        go func(i int, ch chan<- int) {
            defer wg.Done()

            // プールから取得
            o := pool.Get().(*_PooledObject)

            o.Reset()
            o.Set(i)
            v := o.Value()

            // 使い終わったらプールに戻す
            pool.Put(o)

            ch <- v
        }(i, ch)
    }

    go func() {
        defer close(ch)
        wg.Wait()
    }()

    go func(done chan<- struct{}, ch <-chan int) {
        defer close(done)

        for v := range ch {
            output.Stderrl("[output]", v)
        }
    }(done, ch)

    <-done

    return nil
}

実行すると以下のような結果になります。

$ task
task: Task "build" is up to date
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: syncs_use_pool

[Name] "syncs_use_pool"
[New]                Call NewPooledObject()
[output]             103
[output]             105
[New]                Call NewPooledObject()
[output]             104
[output]             106
[output]             107
[output]             108
[output]             109
[output]             110
[output]             111
[output]             112
[output]             113
[output]             114
[output]             115
[output]             116
[output]             117
[output]             118
[output]             119
[output]             101
[output]             100
[output]             102


[Elapsed] 522.83µs

20回利用していますが、実際に生成されたのは2回という結果になっています。

(当然ですが、やる度に結果は異なる可能性があります)

参考情報

Goのおすすめ書籍

Go言語による並行処理

Go言語による並行処理

Amazon

上の書籍の日本語版が下です。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。