1
votes

I have a type that contains a sync.Map where the key in the map is a string and the value is a slice. My code for inserting items into the map is as follows:

newList := []*Item{item}
if result, ok := map.LoadOrStore(key, newList); ok {
    resultList := result.([]*Item)
    resultList = append(resultList, item)
    map.Store(key, resultList)
}

This is not concurrency-safe because the the slice can be loaded and modified by multiple calls concurrently. This code is very fragile so I've attempted to modify it to be:

newList := []*Item{item}
if result, ok := map.LoadOrStore(key, &newList); ok {
    resultList := result.(*[]*Item)
    *resultList = append(*resultList, item)
}

All this does is make the issues occur deterministically. So, I'm trying to find a way to have a map-of-slices that can be added to concurrently. My instinct is to use sync.Mutex to lock the list while I'm adding to it but in order to maintain the concurrent access to the sync.Map I would need to create a map of sync.Mutex objects as well, like this:

newLock := sync.Mutex{}
raw, _ := lockMap.LoadOrStore(key, &newLock)
lock := raw.(*sync.Mutex)

newList := []*Item{item}
if result, ok := map.LoadOrStore(key, &newList); ok {
    lock.Lock()
    resultList := result.(*[]*Item)
    *resultList = append(*resultList, item)
    lock.Unlock()
}

Is there an easier way to go about this?

1
How about a map of struct { *sync.Mutex, []*Item }? - phonaputer
@phonaputer That's an interesting solution. I hadn't thought of that - Woody1193
Well, it's not too different from your current idea. But it is nice to be able to do something like SafeItems.Lock() using the embedded *sync.Mutex. And it's nice to not have to deal with two maps. - phonaputer
Agreed. I implemented it and it fixed my issue. Would you like to claim the answer for this question? - Woody1193
Sure I will take you up on that. - phonaputer

1 Answers

2
votes

It isn't very different from your current plan, but you could save yourself the trouble of handling two maps by using a struct with an embedded mutex for the values of the map.

The struct would look something like this:

type SafeItems struct {
    sync.Mutex
    Items []*Item
}

And it could be used like this:

newMapEntry := SafeItems{Items: itemPtrList}
if result, ok := map.LoadOrStore(key, &newMapEntry); ok {
    mapEntry := result.(*SafeItems)
    mapEntry.Lock()
    mapEntry.Items = append(mapEntry.Items, item)
    mapEntry.Unlock()
}

It's not a huge change but it does provide some syntactic sugar.