Goでデザインパターン (Singletonパターン)

Goデザインパターン

Singletonパターンとは

特定のクラスのインスタンスが1つしか生成されないことを保証するためのデザインパターン。
Singletonパターンを利用することで下記のようなメリットが得られる。

  • 複数のインスタンスを利用することによるバグの発生リスクをなくせる。
    (単一のインスタンスを利用することを強制できる)

利用されそうなものとしては「システム設定を保持するインスタンス(Config設定)」「ログ出力」等。

クラス図

GoのSingletonパターンはクラス構造より、パッケージの初期化によって実装されうるのであまりクラス図には意味がないかも。

実装

package singleton

import (
	"time"
	"fmt"
)

var instance *singleton

// 他パッケージに非公開
type singleton struct {
	id int
}

// パッケージの初期化時にインスタンスを作成
func init() {
	fmt.Println("start initialize")
	instance = &singleton{id: 1}
	fmt.Printf("init: %p\n", instance)
}

// インスタンスの受け渡しはこのメソッドを通して行う
func GetSingleton() *singleton {
	time.Sleep(1 * time.Second)
	return instance
}

実行結果


import (
	"fmt"
	"sync"
	. "singleton/singleton"
)

func main() {
	fmt.Println("start main")
	
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			obj := GetSingleton()
			fmt.Printf("get%d: %p\n", i, obj)
		}(i)
	}

	wg.Wait()
}
start initialize
init: 0xc000018088
start main
get4: 0xc000018088
get2: 0xc000018088
get0: 0xc000018088
get6: 0xc000018088
get3: 0xc000018088
get1: 0xc000018088
get9: 0xc000018088
get8: 0xc000018088
get7: 0xc000018088
get5: 0xc000018088

補足

NGパターン

singletonにする場合、スレッドセーフとなるように気を付ける必要がある。下記のようにGetSingleton()の初回実行時にインスタンスを作成する方法ではスレッドセーフにならない。

package singleton

import (
	"time"
	"fmt"
)

var instance *singleton

type singleton struct {
	id int
}

// 下記ではスレッドセーフにならないためNG
func GetSingleton() *singleton {
    if instance == nil {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        instance = &singleton{id: 1}
        fmt.Printf("init: %p\n", instance)
    }
    return instance
}
start main
init: 0xc000186000
get1: 0xc000186000
init: 0xc000202000
get8: 0xc000202000
init: 0xc000186010
get7: 0xc000202000
init: 0xc000202008
get2: 0xc000202008
init: 0xc000202010
get5: 0xc000186028
init: 0xc000186028
get0: 0xc000186028
init: 0xc000186020
get4: 0xc000186028
init: 0xc000186038
get3: 0xc000202018
init: 0xc000104000
get9: 0xc000202018
init: 0xc000202018
get6: 0xc000202018

main.goは実装欄のものと同じものを実行した。見てわかる通り初期化処理が複数回実行され、インスタンスアドレスも単一のものとなっていない。→Singletonになっていない。

別実装

sync.Onceを利用した実装

sync.Onceを利用してインスタンスの初期化処理を1度しか実行しないように制御する。

package singleton

import (
	"time"
	"fmt"
	"sync"
  "math/rand"
)

var instance *singleton
var once sync.Once

type singleton struct {
	id int
}

func GetSingleton() *singleton {
	once.Do(func() {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        instance = &singleton{id: 1}
        fmt.Printf("init: %p\n", instance)
	})
    return instance
}
start main
init: 0xc000180000
get9: 0xc000180000
get5: 0xc000180000
get6: 0xc000180000
get7: 0xc000180000
get4: 0xc000180000
get0: 0xc000180000
get1: 0xc000180000
get2: 0xc000180000
get8: 0xc000180000
get3: 0xc000180000

Mutexを利用した実装

Mutexを利用してインスタンス生成の有無を判定する前にロックを掛ける。

package singleton

import (
	"time"
	"fmt"
	"sync"
	"math/rand"
)

var instance *singleton
var mu sync.Mutex

type singleton struct {
	id int
}

func GetSingleton() *singleton {
	mu.Lock()
	if instance == nil {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		instance = &singleton{id: 1}
		fmt.Printf("init: %p\n", instance)
	}
	mu.Unlock()
    return instance
}
start main
init: 0xc000086008
get9: 0xc000086008
get0: 0xc000086008
get1: 0xc000086008
get2: 0xc000086008
get3: 0xc000086008
get4: 0xc000086008
get5: 0xc000086008
get6: 0xc000086008
get7: 0xc000086008
get8: 0xc000086008

コメント

タイトルとURLをコピーしました