├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── syncmap.go └── syncmap_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - 1.4 6 | - tip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Yangliang Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEPRECATED syncmap 2 | ======= 3 | 4 | [![GoDoc](https://godoc.org/github.com/DeanThompson/syncmap?status.svg)](https://godoc.org/github.com/DeanThompson/syncmap) [![Build Status](https://travis-ci.org/DeanThompson/syncmap.svg?branch=master)](https://travis-ci.org/DeanThompson/syncmap) 5 | 6 | 7 | **THIS PACKAGE IS DEPRECATED, PLEASE USE THE [sync.Map](https://golang.org/pkg/sync/#Map) PROVIDED BY GO STANDARD LIBRARY.** 8 | 9 | A thread safe map implementation for Golang 10 | 11 | ## Usage 12 | 13 | Install with: 14 | 15 | ```bash 16 | go get github.com/DeanThompson/syncmap 17 | ``` 18 | 19 | Example: 20 | 21 | ```go 22 | import ( 23 | "fmt" 24 | 25 | "github.com/DeanThompson/syncmap" 26 | ) 27 | 28 | func main() { 29 | m := syncmap.New() 30 | m.Set("one", 1) 31 | v, ok := m.Get("one") 32 | fmt.Println(v, ok) // 1, true 33 | 34 | v, ok = m.Get("not_exist") 35 | fmt.Println(v, ok) // nil, false 36 | 37 | m.Set("two", 2) 38 | m.Set("three", "three") 39 | 40 | for item := range m.IterItems() { 41 | fmt.Println("key:", item.Key, "value:", item.Value) 42 | } 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /syncmap.go: -------------------------------------------------------------------------------- 1 | // A thread safe map implementation for Golang 2 | package syncmap 3 | 4 | import ( 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | const ( 11 | defaultShardCount uint8 = 32 12 | ) 13 | 14 | // syncMap wraps built-in map by using RWMutex for concurrent safe. 15 | type syncMap struct { 16 | items map[string]interface{} 17 | sync.RWMutex 18 | } 19 | 20 | // SyncMap keeps a slice of *syncMap with length of `shardCount`. 21 | // Using a slice of syncMap instead of a large one is to avoid lock bottlenecks. 22 | type SyncMap struct { 23 | shardCount uint8 24 | shards []*syncMap 25 | } 26 | 27 | // Create a new SyncMap with default shard count. 28 | func New() *SyncMap { 29 | return NewWithShard(defaultShardCount) 30 | } 31 | 32 | // Create a new SyncMap with given shard count. 33 | // NOTE: shard count must be power of 2, default shard count will be used otherwise. 34 | func NewWithShard(shardCount uint8) *SyncMap { 35 | if !isPowerOfTwo(shardCount) { 36 | shardCount = defaultShardCount 37 | } 38 | m := new(SyncMap) 39 | m.shardCount = shardCount 40 | m.shards = make([]*syncMap, m.shardCount) 41 | for i, _ := range m.shards { 42 | m.shards[i] = &syncMap{items: make(map[string]interface{})} 43 | } 44 | return m 45 | } 46 | 47 | // Find the specific shard with the given key 48 | func (m *SyncMap) locate(key string) *syncMap { 49 | return m.shards[bkdrHash(key)&uint32((m.shardCount-1))] 50 | } 51 | 52 | // Retrieves a value 53 | func (m *SyncMap) Get(key string) (value interface{}, ok bool) { 54 | shard := m.locate(key) 55 | shard.RLock() 56 | value, ok = shard.items[key] 57 | shard.RUnlock() 58 | return 59 | } 60 | 61 | // Sets value with the given key 62 | func (m *SyncMap) Set(key string, value interface{}) { 63 | shard := m.locate(key) 64 | shard.Lock() 65 | shard.items[key] = value 66 | shard.Unlock() 67 | } 68 | 69 | // Removes an item 70 | func (m *SyncMap) Delete(key string) { 71 | shard := m.locate(key) 72 | shard.Lock() 73 | delete(shard.items, key) 74 | shard.Unlock() 75 | } 76 | 77 | // Pop delete and return a random item in the cache 78 | func (m *SyncMap) Pop() (string, interface{}) { 79 | if m.Size() == 0 { 80 | panic("syncmap: map is empty") 81 | } 82 | 83 | var ( 84 | key string 85 | value interface{} 86 | found = false 87 | n = int(m.shardCount) 88 | ) 89 | 90 | for !found { 91 | idx := rand.Intn(n) 92 | shard := m.shards[idx] 93 | shard.Lock() 94 | if len(shard.items) > 0 { 95 | found = true 96 | for key, value = range shard.items { 97 | break 98 | } 99 | delete(shard.items, key) 100 | } 101 | shard.Unlock() 102 | } 103 | 104 | return key, value 105 | } 106 | 107 | // Whether SyncMap has the given key 108 | func (m *SyncMap) Has(key string) bool { 109 | _, ok := m.Get(key) 110 | return ok 111 | } 112 | 113 | // Returns the number of items 114 | func (m *SyncMap) Size() int { 115 | size := 0 116 | for _, shard := range m.shards { 117 | shard.RLock() 118 | size += len(shard.items) 119 | shard.RUnlock() 120 | } 121 | return size 122 | } 123 | 124 | // Wipes all items from the map 125 | func (m *SyncMap) Flush() int { 126 | size := 0 127 | for _, shard := range m.shards { 128 | shard.Lock() 129 | size += len(shard.items) 130 | shard.items = make(map[string]interface{}) 131 | shard.Unlock() 132 | } 133 | return size 134 | } 135 | 136 | // IterKeyWithBreakFunc is the type of the function called for each key. 137 | // 138 | // If false is returned,each key stops. 139 | // Don't modify the SyncMap in this function, 140 | // or maybe leads to deadlock. 141 | type IterKeyWithBreakFunc func(key string) bool 142 | 143 | func (m *SyncMap) EachKeyWithBreak(iter IterKeyWithBreakFunc) { 144 | stop := false 145 | for _, shard := range m.shards { 146 | shard.RLock() 147 | for key, _ := range shard.items { 148 | if !iter(key) { 149 | stop = true 150 | break 151 | } 152 | } 153 | shard.RUnlock() 154 | if stop { 155 | break 156 | } 157 | } 158 | } 159 | 160 | // IterKeyFunc is the type of the function called for every key. 161 | // 162 | // Don't modify the SyncMap in this function, 163 | // or maybe leads to deadlock. 164 | type IterKeyFunc func(key string) 165 | 166 | func (m *SyncMap) EachKey(iter IterKeyFunc) { 167 | f := func(key string) bool { 168 | iter(key) 169 | return true 170 | } 171 | m.EachKeyWithBreak(f) 172 | } 173 | 174 | // Returns a channel from which each key in the map can be read 175 | func (m *SyncMap) IterKeys() <-chan string { 176 | ch := make(chan string) 177 | go func() { 178 | m.EachKey(func(key string) { 179 | ch <- key 180 | }) 181 | close(ch) 182 | }() 183 | return ch 184 | } 185 | 186 | // Item is a pair of key and value 187 | type Item struct { 188 | Key string 189 | Value interface{} 190 | } 191 | 192 | // IterItemWithBreakFunc is the type of the function called for each item. 193 | // 194 | // If false is returned,each item stops. 195 | // Don't modify the SyncMap in this function, 196 | // or maybe leads to deadlock. 197 | type IterItemWithBreakFunc func(item *Item) bool 198 | 199 | func (m *SyncMap) EachItemWithBreak(iter IterItemWithBreakFunc) { 200 | stop := false 201 | for _, shard := range m.shards { 202 | shard.RLock() 203 | for key, value := range shard.items { 204 | if !iter(&Item{key, value}) { 205 | stop = true 206 | break 207 | } 208 | } 209 | shard.RUnlock() 210 | if stop { 211 | break 212 | } 213 | } 214 | 215 | } 216 | 217 | // IterItemFunc is the type of the function called for every item. 218 | // 219 | // Don't modify the SyncMap in this function, 220 | // or maybe leads to deadlock. 221 | type IterItemFunc func(item *Item) 222 | 223 | func (m *SyncMap) EachItem(iter IterItemFunc) { 224 | f := func(item *Item) bool { 225 | iter(item) 226 | return true 227 | } 228 | m.EachItemWithBreak(f) 229 | } 230 | 231 | // Return a channel from which each item (key:value pair) in the map can be read 232 | func (m *SyncMap) IterItems() <-chan Item { 233 | ch := make(chan Item) 234 | go func() { 235 | m.EachItem(func(item *Item) { 236 | ch <- *item 237 | }) 238 | close(ch) 239 | }() 240 | return ch 241 | } 242 | 243 | const seed uint32 = 131 // 31 131 1313 13131 131313 etc.. 244 | 245 | func bkdrHash(str string) uint32 { 246 | var h uint32 247 | 248 | for _, c := range str { 249 | h = h*seed + uint32(c) 250 | } 251 | 252 | return h 253 | } 254 | 255 | func isPowerOfTwo(x uint8) bool { 256 | return x != 0 && (x&(x-1) == 0) 257 | } 258 | 259 | func init() { 260 | rand.Seed(time.Now().UnixNano()) 261 | } 262 | -------------------------------------------------------------------------------- /syncmap_test.go: -------------------------------------------------------------------------------- 1 | package syncmap 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func Test_New(t *testing.T) { 10 | m1 := New() 11 | if m1 == nil { 12 | t.Error("New(): map is nil") 13 | } 14 | if m1.shardCount != defaultShardCount { 15 | t.Error("New(): map's shard count is wrong") 16 | } 17 | if m1.Size() != 0 { 18 | t.Error("New(): new map should be empty") 19 | } 20 | 21 | var shardCount uint8 = 64 22 | m2 := NewWithShard(shardCount) 23 | if m2 == nil { 24 | t.Error("NewWithShard(): map is nil") 25 | } 26 | if m2.shardCount != shardCount { 27 | t.Error("NewWithShard(): map's shard count is wrong") 28 | } 29 | if m2.Size() != 0 { 30 | t.Error("New(): new map should be empty") 31 | } 32 | } 33 | 34 | func Test_Set(t *testing.T) { 35 | m := New() 36 | m.Set("one", 1) 37 | m.Set("tow", 2) 38 | if m.Size() != 2 { 39 | t.Error("map should have 2 items.") 40 | } 41 | } 42 | 43 | func Test_Get(t *testing.T) { 44 | m := New() 45 | v1, ok := m.Get("not_exist_at_all") 46 | if ok { 47 | t.Error("ok should be false when key is missing") 48 | } 49 | if v1 != nil { 50 | t.Error("value should be nil for missing key") 51 | } 52 | 53 | m.Set("one", 1) 54 | 55 | v2, ok := m.Get("one") 56 | if !ok { 57 | t.Error("ok should be true when key exists") 58 | } 59 | if 1 != v2.(int) { 60 | t.Error("value should be an integer of value 1") 61 | } 62 | } 63 | 64 | func Test_Has(t *testing.T) { 65 | m := New() 66 | if m.Has("missing_key") { 67 | t.Error("Has should return False for missing key") 68 | } 69 | 70 | m.Set("one", 1) 71 | if !m.Has("one") { 72 | t.Error("Has should return True for existing key") 73 | } 74 | } 75 | 76 | func Test_Delete(t *testing.T) { 77 | m := New() 78 | m.Set("one", 1) 79 | m.Delete("one") 80 | if m.Has("one") { 81 | t.Error("Delete shoudl remove the given key from map") 82 | } 83 | } 84 | 85 | func Test_Size(t *testing.T) { 86 | m := New() 87 | for i := 0; i < 42; i++ { 88 | m.Set(strconv.Itoa(i), i) 89 | } 90 | if m.Size() != 42 { 91 | t.Error("Size doesn't return the right number of items") 92 | } 93 | } 94 | 95 | func Test_Flush(t *testing.T) { 96 | var shardCount uint8 = 64 97 | m := NewWithShard(shardCount) 98 | for i := 0; i < 42; i++ { 99 | m.Set(strconv.Itoa(i), i) 100 | } 101 | count := m.Flush() 102 | if count != 42 { 103 | t.Error("Flush should return the size before removing") 104 | } 105 | if m.Size() != 0 { 106 | t.Error("Flush should remove all items from map", m.Size()) 107 | } 108 | if m.shardCount != shardCount { 109 | t.Error("map should have the same shardCount after Flush") 110 | } 111 | } 112 | 113 | func Test_IterKeys(t *testing.T) { 114 | loop := 100 115 | expectedKeys := make([]string, loop) 116 | 117 | m := New() 118 | for i := 0; i < loop; i++ { 119 | key := strconv.Itoa(i) 120 | expectedKeys[i] = key 121 | m.Set(key, i) 122 | } 123 | 124 | keys := make([]string, 0) 125 | for key := range m.IterKeys() { 126 | keys = append(keys, key) 127 | } 128 | 129 | if len(keys) != len(expectedKeys) { 130 | t.Error("IterKeys doesn't loop the right times") 131 | } 132 | 133 | sort.Strings(keys) 134 | sort.Strings(expectedKeys) 135 | 136 | for i, v := range keys { 137 | if v != expectedKeys[i] { 138 | t.Error("IterKeys doesn't loop over the right keys") 139 | } 140 | } 141 | } 142 | 143 | func Test_Pop(t *testing.T) { 144 | m := New() 145 | // m.Pop() 146 | 147 | m.Set("one", 1) 148 | 149 | k, v := m.Pop() 150 | if k != "one" && v.(int) != 1 { 151 | t.Error("Pop should returns the only item") 152 | } 153 | if m.Size() != 0 { 154 | t.Error("Size should be 0 after pop the only item") 155 | } 156 | } 157 | 158 | func Test_EachItem(t *testing.T) { 159 | m := New() 160 | for i := 0; i < 42; i++ { 161 | m.Set(strconv.Itoa(i), i) 162 | } 163 | var i *Item 164 | m.EachItemWithBreak(func(item *Item) bool { 165 | i = item 166 | return false 167 | }) 168 | m.Set(i.Key, "new") 169 | if v, ok := m.Get(i.Key); !ok || v.(string) != "new" { 170 | t.Error("value should be an string of value new") 171 | } 172 | 173 | } 174 | 175 | func Test_EachKey(t *testing.T) { 176 | m := New() 177 | for i := 0; i < 42; i++ { 178 | m.Set(strconv.Itoa(i), i) 179 | } 180 | var k string 181 | m.EachKeyWithBreak(func(key string) bool { 182 | k = key 183 | return false 184 | }) 185 | m.Delete(k) 186 | v1, ok := m.Get(k) 187 | if ok { 188 | t.Error("ok should be false when key is missing") 189 | } 190 | if v1 != nil { 191 | t.Error("value should be nil for missing key") 192 | } 193 | } 194 | --------------------------------------------------------------------------------