|
|
|
|
|
by SEMW
952 days ago
|
|
I'm a reasonably new go programmer, I've only been doing it full-time for a few months. But "great concurrency ergonomics" has.. not been my experience. What I've been finding is that primitives it gives you are easy to use, but extremely hard to use _correctly_. My first major PR in go had a week of back and forths as more experienced go programmers on my team pointed out the many, many places where my concurrent code was buggy -- places where I was receiving without selecting over the context being cancelled or some close-channel, places where I was sending from a goroutine that had to have a nonblocking basic loop without guarding with a default clause, places where I was using an unbuffered channel where pathological goroutine scheduling could result in a deadlock, places where I was using non-thread-safe data structures in places that could theoretically be mutated by multiple goroutines and without spamming mutexes everywhere, etc. etc. And sure, I'm relatively new to go. I'll learn these things and get better. But the contrast to some previous work I'd done in Elixir was striking. In Erlang/elixir, _the obvious first thing you try is generally actually correct_. And it gives you standard ways of working built on top of the primitives (genserver calls etc.), so you don't have to buggily reinvent the wheel every time you want to send a message from one goroutine to another with a reply, or need to tear down a bunch of goroutines simultaneously. I have a highly-concurrent elixir service I wrote several years ago, by myself (early stage company, no code review), still in production. It pretty much never has problems and basically just worked from the start. If I'd tried that with go, without a bunch of experienced go programmers to point out all the subtle race conditions, I'd probably still be dealing with a long tail of data races and deadlocks years later. |
|