Live data from Hacker News

A million ways to die from a data race in Go

gaultier.github.io

131–140 of 146 posts

Re: A million ways to die from a data race in Go

#131
post #97

The first Go proverb Rob Pike listed in his talk "Go Proverbs" was, "Don't communicate by sharing memory, share memory by communicating." Go was designed from the beginning to use Tony Hoare's idea of communicating sequential processes for designing concurrent programs. However, like any professional tool, Go allows you to do the dangerous thing when you absolutely need to, but it's disappointing when people insist o…

This is all very nice as an idea or a mythical background story ("Go was designed entirely around CSP"), but Go is not a language that encourages "sharing by communicating". Yes, Go has channels, but many other languages also have channels, and they are less error prone than Go[1]. For many concurrent use cases (e.g. caching), sharing memory is far simpler and less error-prone than using channels.

If you're looking for a language that makes "sharing by communicating" the default for almost every kind of use case, that's Erlang. Yes, it's built around the actor model rather than CSP, but the end result is the same, and with Erlang it's the real deal. Go, on the other hand, is not "built around CSP" and does not "encourage sharing by communicating" any more than Rust or Kotlin are. In fact, Rust and Kotlin are probably a little bit more "CSP-centric", since their channel interface is far less error-prone.

[1] https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-s...

Re: A million ways to die from a data race in Go

#132
post #28

All code is inherently not concurrency-safe unless it says so. The http.Client docs mention concurrent usage is safe, but not modification. The closure compiler flag trick looks interesting though, will give this a spin on some projects.

I agree, any direct / field modification should be assumed to be not-thread safe. OTOH, I think Go made a mistake by exporting http.DefaultClient, because it is a pointer and using it causes several problems including thread safety, and there are libraries that use it. It would have been better if it were http.NewDefaultClient() which creates a new one every time it is called.

I think the original sin of Go is that it neither allows marking fields or entire structs as immutable (like Rust does) nor does it encourage the use of builder pattern in its standard library (like modern Java does).

If, let's say, http.Client was functionally immutable (with all fields being private), and you'd need to have to set everything using a mutable (but inert) http.ClientBuilder, these bugs would not have been possible. You could still share a default client (or a non-default client) efficiently, without ever having to worry about anyone touching a mutable field.

Re: A million ways to die from a data race in Go

#133
post #94

Earlier quoted context omitted.

> Because even Rust makes "Subtle linguistic distinctions" in a lot of places and also in concurrency. Please explain

Runtime borrow checking: RefCell and Rc . Can give other examples, but admittedly they need `unsafe` blocks. Anyways, the article author lacks basic reading skills, since he forgot to mention that the Go http doc states that only the http client transport is safe for concurrent modification . There is no "subtlety" about it. It directly says so. Concurrent "use" is not Concurrent "modification" in Go. The Go stdlib d…

Runtime borrow checking panics if you use the non-try version, and if you're careful enough to use try_borrow() you don't even have to panic. Unlike Go, this can never result in a data race.

If you're using unsafe blocks you can have data races too, but that's the entire point of unsafe. FWIW, my experience is that most Rust developers never reach for unsafe in their life. Parts of the Rust ecosystem do heavily rely on unsafe blocks, but this still heavily limits their impact to (usually) well-reviewed code. The entire idea is that unsafe is NOT the default in Rust.

Re: A million ways to die from a data race in Go

#134

Earlier quoted context omitted.

You can argue about how likely is code like that is, but both of these examples would result in a hard compiler error in Rust. A lot of developers without much (or any) Rust experience get the impression that the Rust Borrow checker is there to prevent memory leaks without requiring garbage collection, but that's only 10% of what it does. Most the actual pain dealing with borrow checker errors comes from it's other j…

Rust concurrency also has issues, there are many complaints about async [0], and some Rust developers point to Go as having green threads. The original author of Rust originally wanted green threads as I understand it, but Rust evolved in a different direction. As for Java, there are fibers/virtual threads now, but I know too little of them to comment on them. Go's green thread story is presumably still good, also re…

Rust has concurrency issues for sure. Deadlocks are still a problem, as is lock poisoning, and sometimes dealing with the borrow checker in async/await contexts is very troublesome. Rust is great at many things, but safe Rust only eliminates certain classes of bugs, not all of them.

Regarding green threads: Rust originally started with them, but there were many issues. Graydon (the original author) has "grudgingly accepted" that async/await might work better for a language like Rust[1] in the end.

In any case, I think green threads and async/await are completely orthogonal to data race safety. You can have data race safety with green threeads (Rust was trying to have data-race safety even in its early green-thread era, as far as I know), and you can also fail to have data race-safety with async/await (C# might have fewer data-race safety footguns than Go but it's still generally unsafe).

[1] https://graydon2.dreamwidth.org/307291.html

Re: A million ways to die from a data race in Go

#135

Earlier quoted context omitted.

Rust concurrency also has issues, there are many complaints about async [0], and some Rust developers point to Go as having green threads. The original author of Rust originally wanted green threads as I understand it, but Rust evolved in a different direction. As for Java, there are fibers/virtual threads now, but I know too little of them to comment on them. Go's green thread story is presumably still good, also re…

Rust has concurrency issues for sure. Deadlocks are still a problem, as is lock poisoning, and sometimes dealing with the borrow checker in async/await contexts is very troublesome. Rust is great at many things, but safe Rust only eliminates certain classes of bugs, not all of them. Regarding green threads: Rust originally started with them, but there were many issues. Graydon (the original author) has "grudgingly ac…

in .NET, async/await does not protect you from data races and you are exposed to them as much as you are in Go, but there is a critical difference in that data races in .NET can never result (not counting unsafe) in memory safety violations. They can and will in Go.

Re: A million ways to die from a data race in Go

#136

Earlier quoted context omitted.

I did say "runtime borrow checking" ie using them together . Example: `Rc::new(RefCell::new(value));`. This will panic at runtime. Maybe I should have used the phrase "dynamic borrowing" ? https://play.rust-lang.org/?version=stable&mode=debug&editio... You don't need different threads. I said concurrency not multi-threading . Interleaving tasks within the same thread (in an event loop for example) can cause panics.

I understand what you meant (but note that allocating an Rc isn’t necessary; &RefCell would work just fine). I just didn’t see the “subtle linguistic distinctions” - and still don’t… maybe you could point them out for me? https://doc.rust-lang.org/stable/std/cell/struct.RefCell.htm... https://doc.rust-lang.org/stable/std/cell/struct.RefCell.htm...

Yeah, it is a crappy example. Ignore me. I just re-read and the rustdoc has no “subtle linguistic distinctions”.

Re: A million ways to die from a data race in Go

#137
post #110
post #49

Earlier quoted context omitted.

This is not true in the general case. E.g. setting a field to true from potentially multiple threads can be a completely meaningful operation e.g. if you only care about if ANY of the threads have finished execution. It depends on the platform though (e.g. in Java it is guaranteed that there is no tearing [1]). [1] In OpenJDK. The JVM spec itself only guarantees it for 32-bit primitives and references, but given that…

> setting a field to true from potentially multiple threads can be a completely meaningful operation e.g. if you only care about if ANY of the threads have finished execution. this only works when the language defines a memory model where bools are guaranteed to have atomic reads and writes so you can't make a claim like "setting a field to true from ... multiple threads ... can be a meaningful operation e.g. if you…

GP didn’t say “setting a ‘bool’ value to true”, it referred to setting a “field”. Interpreted charitably, this would be done in Go via a type that does support atomic updates, which is totally possible.

Re: A million ways to die from a data race in Go

#138
post #43

Earlier quoted context omitted.

> Subtle linguistic distinctions are not what I want to see in my docs, especially if the context is concurrency. Which PL do you use then ? Because even Rust makes "Subtle linguistic distinctions" in a lot of places and also in concurrency.

> Because even Rust makes "Subtle linguistic distinctions" in a lot of places and also in concurrency. Please explain

Not GP but off the top of my head: async cancellation, mutex poisoning, drop+clone+thread interactions, and the entire realm of unsafe (which specific language properties no longer hold in an unsafe block? Is undefined behavior present if there’s a defect in unsafe code, or just incorrect behavior? Both answers are indeed subtle and depend on the specifics of the unsafe block). And auto deref coercion, knowing whether a given piece of code allocates, and “into”/turbofish overload lookup, but those subtleties aren’t really concurrency related.

I like Rust fine, but it’s got plenty of subtle distinctions.

Re: A million ways to die from a data race in Go

#139
post #62

Earlier quoted context omitted.

From where I sit (in Norway), it seems to have become standard corporate-speak in any company where English is widely used. They've even started using the directly translated noun "læring" in Norwegian, too. It's equally silly. Both variants are usually spoken by the type of manager who sets out all future directions based on whatever their LinkedIn circle is talking about. It's thus a very valuable word, because the…

The big question is why is it not proper english, when "teachings" is?

That's a good question. But it's not like English is at all logical in this way ;)

Re: A million ways to die from a data race in Go

#140
post #110

Earlier quoted context omitted.

> setting a field to true from potentially multiple threads can be a completely meaningful operation e.g. if you only care about if ANY of the threads have finished execution. this only works when the language defines a memory model where bools are guaranteed to have atomic reads and writes so you can't make a claim like "setting a field to true from ... multiple threads ... can be a meaningful operation e.g. if you…

GP didn’t say “setting a ‘bool’ value to true”, it referred to setting a “field”. Interpreted charitably, this would be done in Go via a type that does support atomic updates, which is totally possible.

"setting a field to true" clearly means `x.field = value` and not `x.field.Set(value)`
Post reply on HN