Skip to content

Commit b1f44ce

Browse files
authored
Merge pull request #107 from ImVexed/master
Support for 1.18 generics
2 parents 893feb2 + 1d83275 commit b1f44ce

6 files changed

+126
-162
lines changed

.gitignore

-1
This file was deleted.

.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ language: go
77
# You don't need to test on very old version of the Go compiler. It's the user's
88
# responsibility to keep their compilers up to date.
99
go:
10-
- 1.12.x
10+
- 1.18
1111

1212
# Only clone the most recent commit.
1313
git:
@@ -29,4 +29,4 @@ before_script:
2929
# .golangci.yml file at the top level of your repo.
3030
script:
3131
- golangci-lint run # run a bunch of code checkers/linters in parallel
32-
- go test -v -race ./... # Run all the tests with the race detector enabled
32+
- go test -v -race ./... # Run all the tests with the race detector enabled

concurrent_map.go

+47-47
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,29 @@ var SHARD_COUNT = 32
99

1010
// A "thread" safe map of type string:Anything.
1111
// To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards.
12-
type ConcurrentMap []*ConcurrentMapShared
12+
type ConcurrentMap[V any] []*ConcurrentMapShared[V]
1313

1414
// A "thread" safe string to anything map.
15-
type ConcurrentMapShared struct {
16-
items map[string]interface{}
15+
type ConcurrentMapShared[V any] struct {
16+
items map[string]V
1717
sync.RWMutex // Read Write mutex, guards access to internal map.
1818
}
1919

2020
// Creates a new concurrent map.
21-
func New() ConcurrentMap {
22-
m := make(ConcurrentMap, SHARD_COUNT)
21+
func New[V any]() ConcurrentMap[V] {
22+
m := make(ConcurrentMap[V], SHARD_COUNT)
2323
for i := 0; i < SHARD_COUNT; i++ {
24-
m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}
24+
m[i] = &ConcurrentMapShared[V]{items: make(map[string]V)}
2525
}
2626
return m
2727
}
2828

2929
// GetShard returns shard under given key
30-
func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {
30+
func (m ConcurrentMap[V]) GetShard(key string) *ConcurrentMapShared[V] {
3131
return m[uint(fnv32(key))%uint(SHARD_COUNT)]
3232
}
3333

34-
func (m ConcurrentMap) MSet(data map[string]interface{}) {
34+
func (m ConcurrentMap[V]) MSet(data map[string]V) {
3535
for key, value := range data {
3636
shard := m.GetShard(key)
3737
shard.Lock()
@@ -41,7 +41,7 @@ func (m ConcurrentMap) MSet(data map[string]interface{}) {
4141
}
4242

4343
// Sets the given value under the specified key.
44-
func (m ConcurrentMap) Set(key string, value interface{}) {
44+
func (m ConcurrentMap[V]) Set(key string, value V) {
4545
// Get map shard.
4646
shard := m.GetShard(key)
4747
shard.Lock()
@@ -53,10 +53,10 @@ func (m ConcurrentMap) Set(key string, value interface{}) {
5353
// It is called while lock is held, therefore it MUST NOT
5454
// try to access other keys in same map, as it can lead to deadlock since
5555
// Go sync.RWLock is not reentrant
56-
type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{}
56+
type UpsertCb[V any] func(exist bool, valueInMap V, newValue V) V
5757

5858
// Insert or Update - updates existing element or inserts a new one using UpsertCb
59-
func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) {
59+
func (m ConcurrentMap[V]) Upsert(key string, value V, cb UpsertCb[V]) (res V) {
6060
shard := m.GetShard(key)
6161
shard.Lock()
6262
v, ok := shard.items[key]
@@ -67,7 +67,7 @@ func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res i
6767
}
6868

6969
// Sets the given value under the specified key if no value was associated with it.
70-
func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool {
70+
func (m ConcurrentMap[V]) SetIfAbsent(key string, value V) bool {
7171
// Get map shard.
7272
shard := m.GetShard(key)
7373
shard.Lock()
@@ -80,7 +80,7 @@ func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool {
8080
}
8181

8282
// Get retrieves an element from map under given key.
83-
func (m ConcurrentMap) Get(key string) (interface{}, bool) {
83+
func (m ConcurrentMap[V]) Get(key string) (V, bool) {
8484
// Get shard
8585
shard := m.GetShard(key)
8686
shard.RLock()
@@ -91,7 +91,7 @@ func (m ConcurrentMap) Get(key string) (interface{}, bool) {
9191
}
9292

9393
// Count returns the number of elements within the map.
94-
func (m ConcurrentMap) Count() int {
94+
func (m ConcurrentMap[V]) Count() int {
9595
count := 0
9696
for i := 0; i < SHARD_COUNT; i++ {
9797
shard := m[i]
@@ -103,7 +103,7 @@ func (m ConcurrentMap) Count() int {
103103
}
104104

105105
// Looks up an item under specified key
106-
func (m ConcurrentMap) Has(key string) bool {
106+
func (m ConcurrentMap[V]) Has(key string) bool {
107107
// Get shard
108108
shard := m.GetShard(key)
109109
shard.RLock()
@@ -114,7 +114,7 @@ func (m ConcurrentMap) Has(key string) bool {
114114
}
115115

116116
// Remove removes an element from the map.
117-
func (m ConcurrentMap) Remove(key string) {
117+
func (m ConcurrentMap[V]) Remove(key string) {
118118
// Try to get shard.
119119
shard := m.GetShard(key)
120120
shard.Lock()
@@ -124,12 +124,12 @@ func (m ConcurrentMap) Remove(key string) {
124124

125125
// RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held
126126
// If returns true, the element will be removed from the map
127-
type RemoveCb func(key string, v interface{}, exists bool) bool
127+
type RemoveCb[V any] func(key string, v V, exists bool) bool
128128

129129
// RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params
130130
// If callback returns true and element exists, it will remove it from the map
131131
// Returns the value returned by the callback (even if element was not present in the map)
132-
func (m ConcurrentMap) RemoveCb(key string, cb RemoveCb) bool {
132+
func (m ConcurrentMap[V]) RemoveCb(key string, cb RemoveCb[V]) bool {
133133
// Try to get shard.
134134
shard := m.GetShard(key)
135135
shard.Lock()
@@ -143,7 +143,7 @@ func (m ConcurrentMap) RemoveCb(key string, cb RemoveCb) bool {
143143
}
144144

145145
// Pop removes an element from the map and returns it
146-
func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) {
146+
func (m ConcurrentMap[V]) Pop(key string) (v V, exists bool) {
147147
// Try to get shard.
148148
shard := m.GetShard(key)
149149
shard.Lock()
@@ -154,40 +154,40 @@ func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) {
154154
}
155155

156156
// IsEmpty checks if map is empty.
157-
func (m ConcurrentMap) IsEmpty() bool {
157+
func (m ConcurrentMap[V]) IsEmpty() bool {
158158
return m.Count() == 0
159159
}
160160

161161
// Used by the Iter & IterBuffered functions to wrap two variables together over a channel,
162-
type Tuple struct {
162+
type Tuple[V any] struct {
163163
Key string
164-
Val interface{}
164+
Val V
165165
}
166166

167167
// Iter returns an iterator which could be used in a for range loop.
168168
//
169169
// Deprecated: using IterBuffered() will get a better performence
170-
func (m ConcurrentMap) Iter() <-chan Tuple {
170+
func (m ConcurrentMap[V]) Iter() <-chan Tuple[V] {
171171
chans := snapshot(m)
172-
ch := make(chan Tuple)
172+
ch := make(chan Tuple[V])
173173
go fanIn(chans, ch)
174174
return ch
175175
}
176176

177177
// IterBuffered returns a buffered iterator which could be used in a for range loop.
178-
func (m ConcurrentMap) IterBuffered() <-chan Tuple {
178+
func (m ConcurrentMap[V]) IterBuffered() <-chan Tuple[V] {
179179
chans := snapshot(m)
180180
total := 0
181181
for _, c := range chans {
182182
total += cap(c)
183183
}
184-
ch := make(chan Tuple, total)
184+
ch := make(chan Tuple[V], total)
185185
go fanIn(chans, ch)
186186
return ch
187187
}
188188

189189
// Clear removes all items from map.
190-
func (m ConcurrentMap) Clear() {
190+
func (m ConcurrentMap[V]) Clear() {
191191
for item := range m.IterBuffered() {
192192
m.Remove(item.Key)
193193
}
@@ -197,23 +197,23 @@ func (m ConcurrentMap) Clear() {
197197
// which likely takes a snapshot of `m`.
198198
// It returns once the size of each buffered channel is determined,
199199
// before all the channels are populated using goroutines.
200-
func snapshot(m ConcurrentMap) (chans []chan Tuple) {
200+
func snapshot[V any](m ConcurrentMap[V]) (chans []chan Tuple[V]) {
201201
//When you access map items before initializing.
202-
if len(m) == 0{
202+
if len(m) == 0 {
203203
panic(`cmap.ConcurrentMap is not initialized. Should run New() before usage.`)
204204
}
205-
chans = make([]chan Tuple, SHARD_COUNT)
205+
chans = make([]chan Tuple[V], SHARD_COUNT)
206206
wg := sync.WaitGroup{}
207207
wg.Add(SHARD_COUNT)
208208
// Foreach shard.
209209
for index, shard := range m {
210-
go func(index int, shard *ConcurrentMapShared) {
210+
go func(index int, shard *ConcurrentMapShared[V]) {
211211
// Foreach key, value pair.
212212
shard.RLock()
213-
chans[index] = make(chan Tuple, len(shard.items))
213+
chans[index] = make(chan Tuple[V], len(shard.items))
214214
wg.Done()
215215
for key, val := range shard.items {
216-
chans[index] <- Tuple{key, val}
216+
chans[index] <- Tuple[V]{key, val}
217217
}
218218
shard.RUnlock()
219219
close(chans[index])
@@ -224,11 +224,11 @@ func snapshot(m ConcurrentMap) (chans []chan Tuple) {
224224
}
225225

226226
// fanIn reads elements from channels `chans` into channel `out`
227-
func fanIn(chans []chan Tuple, out chan Tuple) {
227+
func fanIn[V any](chans []chan Tuple[V], out chan Tuple[V]) {
228228
wg := sync.WaitGroup{}
229229
wg.Add(len(chans))
230230
for _, ch := range chans {
231-
go func(ch chan Tuple) {
231+
go func(ch chan Tuple[V]) {
232232
for t := range ch {
233233
out <- t
234234
}
@@ -239,9 +239,9 @@ func fanIn(chans []chan Tuple, out chan Tuple) {
239239
close(out)
240240
}
241241

242-
// Items returns all items as map[string]interface{}
243-
func (m ConcurrentMap) Items() map[string]interface{} {
244-
tmp := make(map[string]interface{})
242+
// Items returns all items as map[string]V
243+
func (m ConcurrentMap[V]) Items() map[string]V {
244+
tmp := make(map[string]V)
245245

246246
// Insert items to temporary map.
247247
for item := range m.IterBuffered() {
@@ -251,15 +251,15 @@ func (m ConcurrentMap) Items() map[string]interface{} {
251251
return tmp
252252
}
253253

254-
// Iterator callback,called for every key,value found in
254+
// Iterator callbacalled for every key,value found in
255255
// maps. RLock is held for all calls for a given shard
256256
// therefore callback sess consistent view of a shard,
257257
// but not across the shards
258-
type IterCb func(key string, v interface{})
258+
type IterCb[V any] func(key string, v V)
259259

260260
// Callback based iterator, cheapest way to read
261261
// all elements in a map.
262-
func (m ConcurrentMap) IterCb(fn IterCb) {
262+
func (m ConcurrentMap[V]) IterCb(fn IterCb[V]) {
263263
for idx := range m {
264264
shard := (m)[idx]
265265
shard.RLock()
@@ -271,15 +271,15 @@ func (m ConcurrentMap) IterCb(fn IterCb) {
271271
}
272272

273273
// Keys returns all keys as []string
274-
func (m ConcurrentMap) Keys() []string {
274+
func (m ConcurrentMap[V]) Keys() []string {
275275
count := m.Count()
276276
ch := make(chan string, count)
277277
go func() {
278278
// Foreach shard.
279279
wg := sync.WaitGroup{}
280280
wg.Add(SHARD_COUNT)
281281
for _, shard := range m {
282-
go func(shard *ConcurrentMapShared) {
282+
go func(shard *ConcurrentMapShared[V]) {
283283
// Foreach key, value pair.
284284
shard.RLock()
285285
for key := range shard.items {
@@ -302,9 +302,9 @@ func (m ConcurrentMap) Keys() []string {
302302
}
303303

304304
//Reviles ConcurrentMap "private" variables to json marshal.
305-
func (m ConcurrentMap) MarshalJSON() ([]byte, error) {
305+
func (m ConcurrentMap[V]) MarshalJSON() ([]byte, error) {
306306
// Create a temporary map, which will hold all item spread across shards.
307-
tmp := make(map[string]interface{})
307+
tmp := make(map[string]V)
308308

309309
// Insert items to temporary map.
310310
for item := range m.IterBuffered() {
@@ -326,13 +326,13 @@ func fnv32(key string) uint32 {
326326

327327
// Concurrent map uses Interface{} as its value, therefor JSON Unmarshal
328328
// will probably won't know which to type to unmarshal into, in such case
329-
// we'll end up with a value of type map[string]interface{}, In most cases this isn't
329+
// we'll end up with a value of type map[string]V, In most cases this isn't
330330
// out value type, this is why we've decided to remove this functionality.
331331

332332
// func (m *ConcurrentMap) UnmarshalJSON(b []byte) (err error) {
333333
// // Reverse process of Marshal.
334334

335-
// tmp := make(map[string]interface{})
335+
// tmp := make(map[string]V)
336336

337337
// // Unmarshal into a single map.
338338
// if err := json.Unmarshal(b, &tmp); err != nil {

0 commit comments

Comments
 (0)