← Back to context

Comment by voidfunc

19 hours ago

Ive been writing Go for over a decade and I still feel like I never quite "got" channels. Every time I use them I need to go consult the manual, and none of the patterns feel obvious which is weird considering the rest of the language feels very obvious.

Too many years of Java and managing Threads and Runnables probably rotted my brain.

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.

      1 reply →

  • 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

  • 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.

It depends really on what you actually want to do. I tend to make a few helper funcs for different kinds of things I want to do. For example, a helper funcs to accept anonymous job funcs and collect output. Then you can compose programs out of those higher level blocks.

Arguably, Java's virtual (green) threads managed through structured concurrency and futures is a superior approach.

  • > superior approach

    superior how -- What does it do better over Go channels in your opinion?

    • Superior in ergonomics - launching several async tasks and combining their results via futures is is much easier compared in Java compared to to Go's low-level, primitive way of doing things. No need to explicitly create channels and wait on them. Go doesn't expose Go-routines as a type and hence you are brow-beaten into laboriously using channels even when there is no real need to do so. I guess this could be all sorted out if the Go stdlib offered some convenient structured concurrency packges.

      2 replies →

  • Arguably, being 10 years late to the party is pretty bad.

    Just how Go adding generics to the language didn't magically fix the billions lines of non-generic Go code, adding virtual threads to Java didn't update its entire ecosystem to take advantage of them.

    Meanwhile, the entire Go ecosystem from the beginning took advantage of goroutines, so all code you'll ever interact with will have excellent support for them.

    • If you make use of a 30 years of library that does simple blocking IO and you call that library from a virtual thread you literally have non-blocking behavior - so your "didn't update it's entire ecosystem" is plain wrong. It's also just a Thread, so even consuming virtual threads by old libs is just fine.

      Also, what 'party'? There is java, go, Haskell and erlang with anything similar. The majority of programming languages don't have such a feature so it's pretty questionable use of word to "be late".

      1 reply →

    • I find that very debatable. Spring Boot, for instance, is just one config line away from automatically using virtual threads in its handlers:

          spring.threads.virtual.enabled=true
      

      We also see library implementations switching to them.