8 ms·
People talk about the lack of generics et al as being Go's downfall but personally what I've found to be the biggest annoyance is the very thing Go boasts to be
by hnlmorg 5y ago
People talk about the lack of generics et al as being Go's downfall but personally what I've found to be the biggest annoyance is the very thing Go boasts to be good at: writing bug/race free multi-threaded code.
Channels are vastly oversold given their actual suitability and worse still, add their own classes of race conditions (deadlocks) so you often fallback to using mutexes for those, hopefully rare, occasions you need to pass a shared stated. And once you're reliant on mutexes then you're really no better off than you were with any other language where threading is an afterthought.
The only real benefit Go brings is the ease of spinning new goroutines, but that can be a double edged sword if you don't understand how to manage them in the first place, and the green thread model making goroutines lightweight. Though even this isn't exactly unique to Go either.
I feel like this is one of Go's biggest weaknesses yet it seldom seems to get the same discussion generics and error handling (both of which are annoyances in specific edge cases but neither of which cause the same level of show stopper bugs).
- qaq 5y agoIs there a language where deadlocks are not possible ?
- ndr 5y agoAvoiding deadlocks is easy if you're lockfree (or otherwise non-blocking), the interesting bit is being data-race free: https://www.ponylang.io/discover/#what-makes-pony-different https://www.ponylang.io/discover/#what-makes-pony-different
- camkego 5y agoNow that's something remarkable about a language. Definitely going to check out Pony now.
- masklinn 5y ago> Avoiding deadlocks is easy if you're lockfree (or otherwise non-blocking) Only if you’re also concurrency free. If you can still have two actors waiting on one another, you’re letter-of-the-law deadlock-free, but your program is still in effect deadlocked. > the interesting bit is being data-race free: Data race freedom is much weaker (and easier) than deadlock freedom, or race condition freedom. Safe rust is free of data races for instance. But still subject to deadlocks, and other race conditions.
- ndr 5y ago> Only if you’re also concurrency free. If you can still have two actors waiting on one another, you’re letter-of-the-law deadlock-free, but your program is still in effect deadlocked. I'm not sure we're using the same definitions. If you're non-blocking you have system wide progress, I'm not sure your examples of two actors mutually waiting on each other qualify. In any case I think Pony qualifies as wait-free too, which avoids this scenario. Of course one can still have an infinite for loop, or an infinite ping-pong between actors.
- gpderetta 5y ago> I'm not sure your examples of two actors mutually waiting on each other qualify Of course it qualifies, that's a classical text book example of deadlock.
- ndr 5y agoIsn't that a live lock? Can anything really shield you from that? It seems nothing can, in the same way nothing can really shield you from an infinite loop.
- masklinn 5y ago> in the same way nothing can really shield you from an infinite loop. Fwiw that’s possible with dependent typing, the compiler can require a proof of progress (and thus termination).
- gpderetta 5y ago> Isn't that a live lock? If they are blocked it is not a live lock. If they are spinning in a try_read then it would be a live lock. > Can anything really shield you from that? It seems nothing can, in the same way nothing can really shield you from an infinite loop Non-Turing Complete languages can be proven to terminate(they do exist, see total functional programming). Similarly I think there are programming paradigms that might be truly deadlock free but my recollection is fuzzy. IIRC writes must never block and reads must handle all blocking conditions with guarded commands. I think you can still livelock (by failing to do something when woken up for example).
- hnlmorg 5y agoNone that I've used. But some are better than others for mitigating such scenarios. Go is pretty poor in that regard. I don't think it helped that the early releases were single threaded by default -- the argument being to help with developer onboarding. But really pain points like these need to be flushed out while the language was still being defined rather than hidden away.
- zaphirplane 5y agoUnlikely in a general purpose language I expect you have to go out of your way to make it happen in erlang and actor based systems
- scatters 5y agoYes, Idris. Dependent types in an effect system allow you to prove that deadlock cannot occur, such that a potential deadlock in user code is a compile time error. Edwin Brady has been working on this for decades[1], and Idris 2[2] has finally made it a reality[3]. 1. https://www.type-driven.org.uk/edwinb/papers/fi-cbc.pdf https://www.type-driven.org.uk/edwinb/papers/fi-cbc.pdf (free; 2009) 2. https://livebook.manning.com/book/type-driven-development-with-idris/chapter-15/ https://livebook.manning.com/book/type-driven-development-wi... (registration wall; 2017) 3. https://research-repository.st-andrews.ac.uk/handle/10023/23560 https://research-repository.st-andrews.ac.uk/handle/10023/23... (free; 2021)
- arp242 5y agoYes, I fully agree. The basics and primitives are very simple and easy, but actually using those to make useful packages is far harder than it might seem at first glance. Goroutines and channels are not bad at all, but I feel there's some part missing to use them effectively without too much headaches. I've often found sync.WaitGroup and mutexes far more useful than channels. Actually, I wrote an entire article about this, discussed here: https://news.ycombinator.com/item?id=26220693 https://news.ycombinator.com/item?id=26220693
- eptcyka 5y agoGenerics could for instance have helped implement a mutex that disallows access to the memory that needs to be synchronized without locking it first. This of course wouldn't have helped here, as the root cause was holding on to the mutex for too long. I was under the impression that Go had first party tooling for deadlock checking, something called threadanalyzer if I recall correctly.
- masklinn 5y ago> Generics could for instance have helped implement a mutex that disallows access to the memory that needs to be synchronized without locking it first. Did any language actually do that before rust? It's also somewhat less useful without compiler-checked ownership as you can easily have the protected data escape the lock.
- gpderetta 5y agosync-like classes [1] have been used for a long time in C++. I'm pretty sure that Stroustrup had one in his book. Of course it is always easy to subvert this kind of protection in C++ as you can easily leak references outside of the implicit critical section. [1] https://gcc.godbolt.org/z/3Wefz3cjK https://gcc.godbolt.org/z/3Wefz3cjK
- mikevm 5y agoI don't think I've ever seen anyone saying that Go is supposed to be good at writing bug/race-free multi-threaded code.
- jonathanstrange 5y agoI concur. Although I perfectly understand why they were chosen, Go's concurrent synchronization primitives are not a good design choice. The developers of Go wanted to have primitives out of which better abstractions can be built while leaving freedom to the programmer, it makes sense and is consistent with Go's philosophy. In my experience, however, there is more buggy concurrent code out there than code that works correctly, and this could have been avoided by some higher level abstraction like the actor model or Ada's tasking with Rendezvous (where the latter is not so different from channels, to be fair). It's a real problem because of 3rd party libraries. Too many 3rd party libraries produce deadlocks in unusual scenarios, and it can take almost as much time to write the library on your own than to find out whether their use of goroutines is correct. Deadlocks are really everywhere. The same with sync.atomic by the way, the use of these primitives is rarely correct and people keep using them despite the warnings in the docs. I'm not claiming to be smarter, on the contrary, the problem really is that Go's choice of concurrency primitives is only suitable for concurrency experts and most programmers (including myself) aren't.