
Are Go maps sensitive to data races? - spacey
http://dave.cheney.net/2015/12/07/are-go-maps-sensitive-to-data-races
======
alkonaut
Any data structure that isn't explicitly designed for concurrent access will
not work when used concurrently.

Since a concurrent map (aka. Dictionary aka. Hashtable) is a lot more complex
and slow than a non concurrent one, it's very very unlikely that Go would ship
with its standard map type being a concurrent one. It would be a huge waste.

More likely there is a separate concurrent map type. It's exactly the same in
other languages with mutable collections (Java, .NET, C++, ...).

~~~
chrisseaton
> Since a concurrent map (aka. Dictionary aka. Hashtable) is a lot more
> complex and slow than a non concurrent one, it's very very unlikely that Go
> would ship with its standard map type being a concurrent one. It would be a
> huge waste.

We should develop a system where maps are unsynchronised by default, but when
you access them from a second thread they are then converted transparently by
the runtime to a thread-safe implementation.

~~~
tomp
I'm not sure that's possible. If the first thread isn't explicitly
synchronizing the memory, it won't even detect when the map was changed (made
thread-safe) by the second thread!

A half-solution could be using very lightweight synchronization (e.g. a MVar
containing an unsynchronized map) that's later converted as you describe, but
it would still incur _some_ cynchronization overhead (even for single-threaded
use).

~~~
chrisseaton
You could use the existing hardware memory protection mechanism, if you can do
that per-thread. Each thread allocates into memory protected from other
threads by default, and on a page fault caused by access from another thread
it's then moved into shared memory, and converted to concurrent if needed.

You could then profile allocation sites so that if a map is frequently
converted to concurrent, you then start allocating it as concurrent in the
first place.

~~~
tomp
But how would the first thread know when the map/data structure has been
moved? I guess you could cause a page-fault in the first thread after the
move, although that would rely on inter-CPU synchronizaiton of kernel memory
maps...

~~~
chrisseaton
The same way a thread learns about an object being moved by the GC - change
all the pointers to point to the new one when you move it. Do this in a
safepoint so access to the object is not interrupted.

~~~
tomp
So... the second thread needs to wait for the GC in order to modify the map
concurrently? I mean, it is an optimization for the most common case (single-
threaded access), but it imposes a quite high penalty for concurrent access...

Although I could imagine a similar "signaling" mechanism, where each thread
would periodically read a single snychronized variable, to check if there's
any additional "re-synchronisation" work it needs to do. But I'm guessing that
CPUs are implemented that way anyways.

~~~
chrisseaton
> So... the second thread needs to wait for the GC in order to modify the map
> concurrently?

No nobody needs to wait for a GC, but we use the same mechanism that the GC
does.

> Although I could imagine a similar "signaling" mechanism, where each thread
> would periodically read a single synchronised variable

And that's how the GC already works. Except instead of a variable normally a
'test' instruction is used on a page of memory, and instead of setting the
variable, the permission on the page are changed triggering a page fault.

~~~
tomp
> Except instead of a variable normally a 'test' instruction is used on a page
> of memory, and instead of setting the variable, the permission on the page
> are changed triggering a page fault.

Which GCs do that? The only one I know is Azul's Pauseless JVM GC, but that's
a kind of a special case, given that it needs support from the kernel.

~~~
chrisseaton
All of HotSpot's GCs for example. Here's a survey of different approaches for
stopping in GC.

[http://dl.acm.org/authorize.cfm?key=N98613](http://dl.acm.org/authorize.cfm?key=N98613)

Also note that Azul almost certainly has the same mechanism to do things like
stop the world for dynamic class loading, even if it isn't using it for GC
when running the pauseless collector.

And Section 4 and 5 of this paper talks a bit more about it's implemented
[http://chrisseaton.com/rubytruffle/icooolps15-safepoints/saf...](http://chrisseaton.com/rubytruffle/icooolps15-safepoints/safepoints.pdf).

~~~
tomp
Thanks, I'll check these out!

------
JulianMorrison
The other reason: things _inside_ maps are often pointer types with mutable
data. Even if access to the map is properly locked, if two threads take a
pointer out of it and directly or indirectly keep a reference (for example,
re-slicing a slice without copying it), then one can stomp on the data the
other is reading.

The fixes for this are subtle and annoying. Either you have to lock around
your data, make it somehow immutable, or force copying rather than referencing
(by making it "value" data).

Go would really benefit from a port of Clojure's data structures.

------
kasey_junk
A concurrent map is the top of my wants list for go by a long stretch. The
next thing on that list is a high performance concurrent queue. That these
things aren't available is a direct result of the decision to not support user
defined generics in go.

~~~
_ak
> The next thing on that list is a high performance concurrent queue.

That's called a channel in Go.

~~~
kasey_junk
A channel is a simple abstraction around a giant mutex. It does not scale
throughput very well and has terrible latency performance to boot.

Something like the disruptor ([https://lmax-
exchange.github.io/disruptor/](https://lmax-exchange.github.io/disruptor/)) is
more along the lines of what I was thinking of. Without generics support you
have to write it over and over again for each use case.

~~~
chrisseaton
A go channels implemented using a mutex? I would be surprised if that was the
case (but it might be). I would imagine such a fundamental construct built
into the language would be lock-free.

~~~
kasey_junk
[https://golang.org/src/runtime/chan.go](https://golang.org/src/runtime/chan.go)

Lines 28, 152, 401

~~~
chrisseaton
I stand corrected. I guess it must have some more complex functionality that I
thought, needing a lock.

~~~
kasey_junk
That is the charitable way to interpret it.

------
zalmoxes
Mentioned in the FAQ:
[https://golang.org/doc/faq#atomic_maps](https://golang.org/doc/faq#atomic_maps)

------
grabcocque
"no, there is nothing wrong with Go’s map implementation."

"Getting your locking wrong will corrupt the internal structure of the map."

Ahem.

If you have a non-concurrency-safe map implementation in a language that's
advertised as being good for concurrency, there is _definitely_ something
wrong.

~~~
4ad
> Ahem

Maps are not concurrency-safe, you know what else isn't? Integers, floats,
pointers, structs. And not only in Go, but in pretty much almost all languages
with mutable state, like C and C++.

The map implementation in Go is consistent with the rest of the language, plus
it allows for great performance under some rather common scenarios.

But I guess it's fashionable to snark on message boards than rather understand
all these facts.

~~~
grabcocque
I apologise for expecting better of language designers. After all, the
solutions to these sorts of issues has only been around for a couple of
decades. Far too soon.

~~~
alkonaut
This is a library thing, not a language thing. And it's a performance
tradeoff: they could have shipped with a concurrent map only, that is 1/10 as
fast as the current one, but never causes a data race. However everyone that
_isn 't_ doing anything concurrently would be paying for it. Not very clever.
I trust the class library designers thought this through. They reached the
same conclusion as pretty much _every other library designer in every other
language_. For the same reasons.

~~~
titzer
Data races are not the same thing as non-atomic accesses.

~~~
alkonaut
Sorry, I interpreted the statement as being about concurrency in maps, not
about atomicity.

------
khgvljhkb
Golang devs should have a look at Clojure and it's `core.async` library. It
works very similarly to Go (heavily inspired by the good stuff), but all data
structures are persistent, meaning you will never have problems with
concurrent mutations (because there there are no mutations in the API, only
internally).

I recommend Go users to install the boot utility, and playing around with
Clojure & core.async. These communities can share many things.

~~~
muraiki
It's been a while since I used Clojure, so I apologize if what I'm saying is
wrong or out of date. Having used both Clojure and Go there are some
significant differences in how coroutines work in each.

For instance, in Go making a blocking system call inside of a goroutine "just
works" as Go will create additional goroutines as necessary. But if you do the
equivalent in Clojure, you risk thread starvation.

"However if you are using go blocks and blocking IO calls you're in trouble.
You will in fact often get worse performance than using threads (in the normal
case) since you will quickly hog all the threads in the go block thread pool
and block out any other work! ... Since the go block thread pool is quite
small, it's easy to block all the threads and thus stopping all 'go
processing'."[0]

Now there are some ways around this; I think that you can use promises for
blocking IO inside of a Clojure coroutine. But this is one area where you
don't have to worry about this kind of thing in Go, even if you do have to
know that the default map implementation isn't thread safe. :)

[0]
[http://martintrojer.github.io/clojure/2013/07/07/coreasync-a...](http://martintrojer.github.io/clojure/2013/07/07/coreasync-
and-blocking-io/)

~~~
JoeAltmaier
Its problematic to mix blocking and message-based paradigms together. Its kind
of worse than either one alone, because added complexity. Yet its so hard to
come up with an entirely blockless design, especially since OSs won't
cooperate (rarely have non-blocking APIs throughout).

------
SixSigma
Share memory by communicating; don't communicate by sharing memory.

[https://golang.org/doc/codewalk/sharemem/](https://golang.org/doc/codewalk/sharemem/)

~~~
JulianMorrison
Easy to say, hard to do.

You need to make sure that once you've done with the item and passed it on,
you don't have any overt, tacit, or deeply-buried pointers into the item you
just let go of.

------
vinceyuan
> _Go maps are not goroutine safe_

Which types in Go are goroutine safe? Do we need to use sync.Mutex for all
global variables which are read and written in goroutines? My understanding is
only read-only variables are goroutine safe.

~~~
JulianMorrison
If you copy value data inside a mutex, access to the copy you made is
goroutine safe.

