Comment by vrosas
18 hours ago
Channels are honestly one of the most over-used things in Go. I've been writing Go professionally since 2015 and I honestly rarely use them. Programmers new to Go love to shovel them in everywhere because "why use Go if you're NOT going to use channels?" and I have to say sorry, no - write it serially, then determine if it breaches your SLOs, THEN determine if concurrency fixes it.
Using Go since 1.0, agree wholeheartedly. Newcomers read the docs and start throwing channels everywhere because why not.
I always ask/tell people to write without channels, and only add them when you have justification for doing so. That leads to much more sane code.
One pattern I see often because random blogs mention it is starting X long lived goroutines, then passing them data via channels, then receiving responses via channels, then handling. In my experience, it's 100x less error prone to just use a semaphore to start a goroutine per data, and have them do their own handling. No channels involved.
Yup. Go maturity is realising how little you need to use channels and Goroutines. You probably just need a setup in one place, like in front of incoming requests ... which using net/http already does for you.
Spamming them all over the place is a red flag imo
Very interesting feedback. I'm a Go newbie and the goroutine/channel duality sounds delightful from where I stand, but once again I have no professional experience with Go yet, only sample programs to get used to the language.
One question though: your advice is to write things serially first before moving to concurrency, which for me is general programming common sense, but would you argue that once you start writing concurrent code then channels are not well suited compared to "good old" sync primitives (mutexes, etc.)?
There are a lot of places where channels look like the correct primitive but may actually be overkill. One of my favorite examples is collecting results from a group of goroutines. If you know the number of results up front, you can just define a slice and give each thread an index of the slice to write to (and a waitgroup of course). No channels, no mutexes, and completely thread safe.
Wouldn't the example you described result in false sharing?
I’d say it’s important to understand how they work but I also rarely find myself reaching for channels. I see more usage of wait groups and mutexes, but even then you can build abstractions around these in a way that can be reused without having to touch them again.
What kind of patterns?
I've started using golang last year and I feel like I'm missing exactly this kind of experience with these patterns
Bolting on concurrency after-the-fact is notoriously difficult to do.
Concurrency has nothing to do with performance and everything to do with your domain. If what you're modeling is concurrent, your code should accordingly be concurrent also.