こんにちは。KOUKIです。とある企業でWeb系の開発エンジニアをしています。
Go言語でWeb開発をしていますが、最近ローカルキャッシュの実装をsync.Mapを使って実装したので、使い方を備忘として残しておきます。
<目次>
Sync.Mapとは
sync.Mapは、Go言語のMapの様にメモリにKey/Valueの形式で値を保存できる仕組みです。
内部的にsyncパッケージのMutexでリソースの排他制御を行ってくれるので、goroutine上で動かしても安全で、使いやすいです。
使える機能は以下の通りです。
1 2 3 4 5 6 7 |
type Map func (m *Map) Delete(key interface{}) func (m *Map) Load(key interface{}) (value interface{}, ok bool) func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) func (m *Map) Range(f func(key, value interface{}) bool) func (m *Map) Store(key, value interface{}) |
実際に動かしながら学んでいきましょう。
Store/Range
Storeでデータを格納し、Rangeで全てのデータを取り出せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package main import ( "fmt" "sync" "time" ) func main() { // Mapをインスタンス化 m := sync.Map{} // 値の格納 m.Store("Key 1", "Value 1") m.Store("Key 2", "Value 2") m.Store("Key 3", "Value 3") m.Store("Key 4", "Value 4") m.Store("Key 5", "Value 5") // ちょっとまつ <-time.After(5 * time.Millisecond) // 全ての値を取り出す m.Range(func(key interface{}, value interface{}) bool { fmt.Printf("Key: %v(Type: %T) -> Value: %v(Type: %T)\n", key, key, value, value) return true }) } |
データの格納と取り出しはこんな感じです。
直感的でわかりやすいですよね。
1 2 3 4 5 6 |
$ go run main.go Key: Key 3(Type: string) -> Value: Value 3(Type: string) Key: Key 4(Type: string) -> Value: Value 4(Type: string) Key: Key 5(Type: string) -> Value: Value 5(Type: string) Key: Key 1(Type: string) -> Value: Value 1(Type: string) Key: Key 2(Type: string) -> Value: Value 2(Type: string) |
出力条件も絞れますね。
1 2 3 4 5 6 |
m.Range(func(key interface{}, value interface{}) bool { if key != "Key 5" { fmt.Printf("Key: %v(Type: %T) -> Value: %v(Type: %T)\n", key, key, value, value) } return true }) |
1 2 3 4 5 |
$ go run main.go Key: Key 1(Type: string) -> Value: Value 1(Type: string) Key: Key 2(Type: string) -> Value: Value 2(Type: string) Key: Key 3(Type: string) -> Value: Value 3(Type: string) Key: Key 4(Type: string) -> Value: Value 4(Type: string) |
LoadOrStore
LoadOrStoreは、値の格納と読み込みが同時にできるようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package main import ( "fmt" "sync" "time" ) func main() { // Mapをインスタンス化 m := sync.Map{} // 存在しないデータを読み込もうとした場合、その値を格納してから読み込む LOS1, ok := m.LoadOrStore("Key 6", "Value 6") if ok { fmt.Printf("Key 6: %v \n", LOS1) } fmt.Printf("Key 6: %v \n", LOS1) <-time.After(5 * time.Millisecond) } |
1 2 |
$ go run main.go Key 6: Value 6 |
Delete
値の削除も簡単です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
package main import ( "fmt" "sync" "time" ) func main() { // Mapをインスタンス化 m := sync.Map{} // 値の格納 m.Store("Key 1", "Value 1") m.Store("Key 2", "Value 2") m.Store("Key 3", "Value 3") m.Store("Key 4", "Value 4") m.Store("Key 5", "Value 5") m.Range(func(key interface{}, value interface{}) bool { fmt.Printf("Key: %v -> Value: %v\n", key, value) return true }) <-time.After(5 * time.Millisecond) fmt.Println("==================================") // 値を削除する m.Delete("Key 5") m.Range(func(key interface{}, value interface{}) bool { fmt.Printf("Key: %v -> Value: %v\n", key, value) return true }) } |
Delete(Key)でOKです。
1 2 3 4 5 6 7 8 9 10 11 |
$ go run main.go Key: Key 1 -> Value: Value 1 Key: Key 2 -> Value: Value 2 Key: Key 3 -> Value: Value 3 Key: Key 4 -> Value: Value 4 Key: Key 5 -> Value: Value 5 ================================== Key: Key 1 -> Value: Value 1 Key: Key 2 -> Value: Value 2 Key: Key 3 -> Value: Value 3 Key: Key 4 -> Value: Value 4 |
Load
単にデータを読み込みたい場合は、Loadメソッドを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package main import ( "fmt" "sync" ) func main() { // Mapをインスタンス化 m := sync.Map{} // 値の格納 m.Store("Key 1", "Value 1") m.Store("Key 2", "Value 2") m.Store("Key 3", "Value 3") m.Store("Key 4", "Value 4") m.Store("Key 5", "Value 5") LOS1, ok := m.Load("Key 5") if ok { fmt.Printf("Value: %v(Type: %T)\n", LOS1, LOS1) } LOS2, ok := m.Load("hoge") if ok { fmt.Printf("Value: %v(Type: %T)\n", LOS2, LOS2) } else { fmt.Println("No Key-Value") } } |
1 2 3 |
$ go run main.go Value: Value 5(Type: string) No Key-Value |
注意するべき点ですが、Loadの戻り値です。上記ではType: stringと出ているのでstring型が返ってくると思いがちですが、実はinterfaceが返ってきます。
1 2 |
// 実はvalueの戻り値はinterface func (m *Map) Load(key interface{}) (value interface{}, ok bool) |
そのため、キャストが必要になる場合があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func main() { ... LOS1, _ := m.Load("Key 5") var checkType string // cannot use LOS1 (type interface {}) as type string in assignment: need type assertio // checkType = LOS1 // キャストが必要 checkType = LOS1.(string) fmt.Println(checkType) } |
With Goroutine
goroutineと共に使ってもデットロックなどは発生しません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import ( "fmt" "sync" "time" ) func main() { m := sync.Map{} for i := 0; i < 1000; i++ { go func(i int) { m.Store(i, i) <-time.After(5 * time.Millisecond) }(i) } <-time.After(1000 * time.Millisecond) m.Range(func(key interface{}, value interface{}) bool { fmt.Printf("Key: %v -> Value: %v\n", key, value) return true }) } |
1 2 3 4 5 6 7 |
$ go run main.go Key: 69 -> Value: 69 Key: 164 -> Value: 164 Key: 810 -> Value: 810 Key: 852 -> Value: 852 Key: 70 -> Value: 70 ... |
おわり
メソッドの基本的な使い方は以上です。結構簡単でした?
sync.Mapは、Redisのようなキャッシュの使い方ができて個人的には便利だと思っています。
この様にライブラリがシンプルかつ充実しているので、Go言語は本当に描きやすい言語です
本当にオススメですよ。
それでは、また!
最近のコメント