kitoko552.memo

kitoko552のメモ

Goでスレッドセーフなシングルトン

※この記事はQiitaにも投稿されています。

Goでスレッドセーフなシングルトン - Qiita


Goのシングルトンで検索すると、以下のような内容でシングルトンを実現していることが多いです。

package singleton

type single struct {
    // Some fields
}

var sharedInstance *single

func GetInstance() *single {
    if sharedInstance == nil {
        sharedInstance = &single{ /* 初期化 */ }
    }

    return sharedInstance
}

しかし、これでは複数スレッドからほぼ同時にGetInstance()が呼ばれると、複数インスタンスが生成されてしまいます。 例として、以下のようにGetInstance()を変更し、非同期で実行してみます。

package singleton

import (
    "fmt"
    "time"
)

/* 省略 */

func GetInstance() *single {
    if sharedInstance == nil {
        fmt.Println("create new instance.")
        time.Sleep(1000) // 同時にGetInstanceが呼ばれるようわざと時間をかける
        sharedInstance = &single{ /* 初期化 */ }
    }

    return sharedInstance
}
package main

import (
    "./singleton"
)

func main() {
    ch := make(chan interface{})
    go run(ch)
    go run(ch)
    go run(ch)
    <- ch
}

func run(ch chan interface{}) {
    ch <- singleton.GetInstance()
}

結果は以下のようになります。

$ go run main.go
create new instance.
create new instance.
create new instance.

したがって、Goにおいてスレッドセーフなシングルトンを実現したいのであれば、以下のようにするといいと思います。

package singleton

type single struct {
    // Some fields
}

var sharedInstance *single = newSingle()

func newSingle() *single {
    // 何かしらの初期化処理
    return &single{ /* 初期化 */ }
}

func GetInstance() *single {
    return sharedInstance
}

特に初期化処理が必要ないのであれば、以下のようにもっと簡潔にすることもできます。

package singleton

type single struct {
    // Some fields
}

var sharedInstance *single = &single{ /* 初期化 */ }

func GetInstance() *single {
    return sharedInstance
}