3. The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If map entries that have not yet been reached are removed during iteration, the corresponding iteration values will not be produced. If map entries are created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is nil, the number of iterations is 0.
-j
> Is there a better way?Is fn allowed to mutate the map? If not then the locking/unlocking is not necessary at all. If yes then there's a race between acquiring the k,v pair from the map under a lock and calling fn with those, now possibly obsolete values.
The runtime has added lightweight, best-effort detection of concurrent misuse of maps. As always, if one goroutine is writing to a map, no other goroutine should be reading or writing the map concurrently. If the runtime detects this condition, it prints a diagnosis and crashes the program. The best way to find out more about the problem is to run the program under the race detector, which will more reliably identify the race and give more detail.
On Friday, January 6, 2017 at 5:12:54 PM UTC-8, tsuna wrote:Hi there,I have a struct that contains an unexported map protected by a mutex, let’s say something like:type Map struct {mtx sync.Mutexcol map[string]interface{}}I want to implement a ForEach method that works in O(1) space and doesn’t hold the mutex while working on each entry. This is what I have:func (m *Map) ForEach(fn func(k, v interface{})) {m.mtx.Lock()for k, v := range m.col {m.mtx.Unlock()fn(k, v)m.mtx.Lock()}m.mtx.Unlock()}(runnable example: https://play.golang.org/p/tX2lCCYWxq)The language spec says:3. The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If map entries that have not yet been reached are removed during iteration, the corresponding iteration values will not be produced. If map entries are created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is nil, the number of iterations is 0.With the caveats mentioned above regarding concurrent deletions/insertions in mind, is this implementation of ForEach correct? Is there a better way?I'm not sure I understand what you're trying to guarantee.
The code you've shown looks fine, but the devil is in the details of what fn does.The fundamental constraint is that you need to make sure that no two concurrent operations, at least one of which is a write, happen to the map. Treat iteration as a read. And it isn't one read extending for the whole loop. It is one "instantaneous" read each time a new k,v is assigned.So fn can read all it wants.If fn writes, those writes must be synchronized so that they do not happen concurrently with the ForEach goroutine iterating.So if fn writes in the same goroutine that ForEach is running in, that is fine.If fn spawns other goroutines to write to the map, asks other goroutines to write to the map via channels, etc. then those accesses must be properly synchronized.One way to do that is to use the same lock as you used in ForEach around those accesses.
Is there a better way?