いろいろ備忘録日記

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

Goメモ-348 (シングルトンパターン)(design-pattern, singleton)

関連記事

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

概要

以下、自分用のメモです。忘れない内にメモメモ。。。

たまたまですが、以下の記事をみました。

ahsunsights.tech

最初にシングルトンパターンの説明があるのですが、以下のようなコードになってました。

上記記事より引用。

type Singleton struct {
    data string
}

var instance *Singleton

func GetInstance() *Singleton {
    if instance == nil {
        instance = &Singleton{"Initial data"}
    }
    return instance
}

見て、「ん?」ってなりました。これじゃスレッドセーフになってないです。

なので、複数のgoroutineが一気に入ってきたら場合によっては複数作れちゃうのでダメですね。

GetInstanceを呼び出した時に lazy でインスタンスを作成するのはよくやるやり方なので分かるのですが、ここをスレッドセーフにしないとシングルトンの意味が無くなります。

サンプル

個人的にシングルトンを使うことが最近無いのですが、Goの場合は以下のようにしています。

defines.go

シングルトン扱いで処理したい子です。

package defines

import (
    "sync"
)

type (
    Defines struct {
        def1 string
        def2 string
    }
)

func (me *Defines) GetDef1() string { return me.def1 }
func (me *Defines) GetDef2() string { return me.def2 }

var (
    instance *Defines
    once     sync.Once
)

func GetInstance() *Defines {
    once.Do(func() {
        instance = &Defines{
            def1: "hello",
            def2: "world",
        }

        println("create: Defines")
    })

    return instance
}

インスタンスを作成する部分は sync.Once でガードしています。lazyな生成が必要ない場合は varでの宣言時に作っておけばオッケイ。

main.go

使う側。わざと複数のgoroutineで GetIntance() を呼び出すようにしています。

package main

import (
    "fmt"
    "runtime"
    "sync"

    "github.com/devlights/try-golang/examples/singleapp/designpatterns/singleton/defines"
)

func main() {
    if err := run(); err != nil {
        panic(err)
    }
}

func run() error {
    type (
        Singleton interface {
            GetDef1() string
            GetDef2() string
        }
    )

    var (
        c  = runtime.NumCPU()
        l  = make([]Singleton, 0, c)
        wg sync.WaitGroup
    )

    wg.Add(c)
    for i := 0; i < c; i++ {
        go func() {
            defer wg.Done()
            println(">>> call: GetInstance")
            l = append(l, defines.GetInstance())
        }()
    }

    wg.Wait()
    for i := 0; i < c; i++ {
        v := l[i]
        fmt.Printf("%p (%s:%s)\n", v, v.GetDef1(), v.GetDef2())
    }

    return nil
}

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

task: [default] go clean
task: [default] go build
task: [default] ./singleton
>>> call: GetInstance
create: Defines
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
>>> call: GetInstance
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)
0xc00007c020 (hello:world)

参考情報

try-golang/examples/singleapp/designpatterns/singleton at main · devlights/try-golang · GitHub

Goのおすすめ書籍

Go言語による並行処理

Go言語による並行処理

Amazon


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

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