7 ms·
Are Go maps sensitive to data races?
- grabcocque 11y ago"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.
- iand 11y agoThe two sentences are consistent once you read that Go maps are not designed to be safe for concurrent access.
- grabcocque 11y agoJava's (pre-concurrent) HashMaps aren't designed for concurrent access. Do you know what they do? They throw a ConcurrentModificationException. Do you know what they don't do? Silently corrupt themselves.
- vbezhenar 11y agoFrom HashMap JavaDoc: Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs. So even while Java tries to find concurrency bugs, it does not promise it.
- 4ad 11y agoNot to mention that Go does have the race detector, which does the same thing in this case (although it does so much more in general).
- lmm 11y agoIndeed it doesn't promise it. But in practice it's very good at it - the reason they have to put that note in there is because if you made a program whose correctness depended on it, it would be a long time before you saw the bug.
- alkonaut 11y agoThat check probably comes at a performance cost that the Go standard library designers found unacceptable. You don't even need to be modifying it concurrently to read inconsistent state, it's enough to write on one thread and read on another, while the map is updating internally. So in order to prevent inconsistent reads you would have to check for concurrent access on all reads. The .NET BCL Dictionary is just like the Go one: it does no checking for you and will silently corrupt or return an inconsistent view if you access it concurrently. The whole reason there are two implementations of the dictionary (Concurrent and Non concurrent) is that the performance cost of the concurrent implementation is too high to carry for the non-concurrent one. Depending on the cost of checking for concurrent access, a lot of the gain of using a simpler non-concurrent one could be lost.
- shellac 11y agoThe check is actually very cheap -- you can check the source yourself -- but as you might expect it won't catch all unsafe use.
- alkonaut 11y agoLike you suggest you can make a cheap check that sometimes triggers an exception. Or an expensive check that always triggers on all concurrent access. As far as I can see in the java HashMap source, the concurrent modification check is actually only done for the enumerator (?) so the problem of multiple concurrent insertions, or even reading-while-inserting will never be caught anyway. Just like Go, or .NET. A check for single thread access (which is even stricter than non-concurrent access which allows several threads as long as they aren't used concurrently) would be pretty cheap: store the thread ID on creation and then verify on reads and writes. Failing on concurrent access otherwise on all reads and writes would basically mean that you add a lock to the write operation, and fail any reads while anyone is writing. This however is close enough to making it a full concurrent map, so it isn't worth it.
- binarycrusader 11y agoWell, they believed it was worth adding something (from a commit today): https://github.com/golang/go/commit/50c5042047be3af36e7bb478435093ea45e8f1f0 https://github.com/golang/go/commit/50c5042047be3af36e7bb478...
- noselasd 11y agoA Java Hastable is threadsafe, and is documented to be so A Java HashMap is not threadsafe and is documented to be so, and can silently corrupt data, can go into an infinite loop etc. if you mess with it from several threads. Both leaves you with a lot of potential for races in application logic though.
- coldtea 11y agoYeah, but the parent's phrase was: "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". If we accept his premise, then the fact that "they wasn't designed for that" is no excuse. It's like selling a kids toy that has tiny choke-inducing parts. In that case, just saying: "It wasn't designed to be eaten by kids" is not really an excuse.
- arethuza 11y agoI think there is a big difference between "being good for concurrency" and preventing all concurrent programming problems - concurrency is hard and while go does seem to provide a lot of nice structures to make it more straightforward it can't, and doesn't appear to claim to, hide all the complexities of concurrency from developers.
- 4ad 11y agoFirst, that statement was not present in grandparent's post when the parent posted his message. Second, that premise is completely untrue. Go, and most other languages give you more-or-less orthogonal primitives and let you combine them. It's okay for your types not to be thread safe, if by combining them with some other primitive you can make them thread safe. Every other language does this. Some offer you a concurrent map along with a non-concurrent map. For many reasons I won't get into, Go only has one kind of map and lets the user do the rest (which is an extremely easy idiom in Go). Note that even though many languages conveniently offer concurrent maps, no languages with mutable state that I know of offer concurrent integer and concurrent structs. The user is still responsible for ensuring the safety of those. This is perfectly fine because the grandparent's premise is wrong. In concurrent language, by far the most common case is by data to be owned by a goroutine/thread/whatever. It's very easy to reason about code this way, and it's the way you are encouraged to write code. Only when you need to share data you need to worry about concurrency-safety, and in that scenario you have available all the tools to ensure it.
- lambda_cube 11y ago> no languages with mutable state that I know of offer concurrent integer and concurrent structs Java does for integer http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html http://docs.oracle.com/javase/8/docs/api/java/util/concurren... You can find similar classes in the java.util.concurrent.atomic package. http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html http://docs.oracle.com/javase/8/docs/api/java/util/concurren... I'm not sure what requirements you have for a concurrent struct, but Java has classes to atomically manipulate int and long fields of a class. I'm not arguing your general point (in fact, I agree with it), I'm just supplying some extra information of a language that you apparently don't know.
- 4ad 11y ago> 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 11y agoI 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.
- jrockway 11y agoAll the language designer can do is throw in a lock that you may or may not need if you're already locking properly. ConcurrentModificationException is nice, but comes at a performance cost.
- alkonaut 11y agoThis 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.
- JulianMorrison 11y agoGo's idiomatic way to handle access to maps is: at any one time exactly one thread owns the map. Either pass it around, or have a gatekeeper thread.
- goykasi 11y agoI see nothing wrong with how Go's map implementation was described. Go has never claimed to be completely safe for concurrent operations. Deadlocks, inconsistent state, etc are more than easy to accomplish. But, it still stands up to a "good for concurrency" claim by offering nice primitives for writing concurrent code, a race detector and explicitly stating in the stdlib documentation which libraries are thread-safe -- libraries are to be considered not safe unless explicitly said to be.
- alkonaut 11y agoAny 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 11y ago> 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 11y agoI'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 11y agoYou 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.
- grabcocque 11y agoI'm increasingly convinced that Go is the new PHP, designed and used by the kind of people who know just enough to be incredibly dangerous to themselves and everyone else they touch.
- alkonaut 11y agoI don't understand why someone would write a blog post about a collection type not being thread safe? Why not just look at the documentation and simply confirm that it isn't thread safe? Edit: Ok one reason could be that the documentation sucks and there are no mentions of the concurrency guarantees of the standard library types...
- lazyjones 11y agoPossibly because the Go "authorities" are now trying to attract the kind of programmers who need "trigger warnings" and "safe spaces", for some reason. Just look at the new community CoC. I apologize if this constitutes a micro- or nano-aggression. More seriously, it's probably just a frequent mistake and they try to educate people proactively to help code quality in the long run. Edit: the non-atomic map updates are mentioned in the FAQ, but you're right, the documentation could be improved in that regard (possibly triggering many criticisms of the language from people who've never used it, just browsed the docs for reasons not to use it).
- lambda_cube 11y agoCoC? Code of Conduct? Churches of Christ? Call of Cthulhu?
- AYBABTME 11y agoYou say that, while also suggesting that Java's best effort ConcurrentModificationException (something that will be thrown if you're lucky) is better. Suggests to me that you know just enough to be dangerous.
- lmm 11y ago
- kasey_junk 11y agoA 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 11y ago> The next thing on that list is a high performance concurrent queue. That's called a channel in Go.
- kasey_junk 11y agoA 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 11y agoA 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 11y agohttps://golang.org/src/runtime/chan.go https://golang.org/src/runtime/chan.go Lines 28, 152, 401
- chrisseaton 11y agoI stand corrected. I guess it must have some more complex functionality that I thought, needing a lock.
- JulianMorrison 11y agoThe 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.
- zalmoxes 11y agoMentioned in the FAQ: https://golang.org/doc/faq#atomic_maps https://golang.org/doc/faq#atomic_maps
- SixSigma 11y agoShare memory by communicating; don't communicate by sharing memory. https://golang.org/doc/codewalk/sharemem/ https://golang.org/doc/codewalk/sharemem/
- JulianMorrison 11y agoEasy 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.
- khgvljhkb 11y agoGolang 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 11y agoIt'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-and-blocking-io/ http://martintrojer.github.io/clojure/2013/07/07/coreasync-a...
- JoeAltmaier 11y agoIts 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).
- khgvljhkb 11y agoI did not know this, thank you very much! Enlightening indeed.
- vinceyuan 11y ago> 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 11y agoIf you copy value data inside a mutex, access to the copy you made is goroutine safe.