You do the same thing, if that's really the architecture you need.
Channels communicating between persistent workers are fine when you need decoupled asynchronous operation like that.
However, channels and detached coroutines are less appropriate in a bunch of other situations, like fork-join, data parallelism, cancellation of task trees, etc. You can still do it, but you're responsible for adding that structure, and ensuring you don't forget to wait for something, don't forget to cancel something.
You can accomplish fork-join, data parallelism, and cancellation of task trees in a with `errgroup` in Go (which provides a way to approach structured concurrency).
So at least those are a subset of Go's concurrency model.
> So at least those are a subset of Go's concurrency model.
That's why the article about structured concurrency compared it to goto. Everything is a subset of goto. It can do everything that structured programming can do, and more! With goto you can implement your own conditions, switches, loops, and everything else.
The problem is not the lack of power, but lack of enforced structure. You can implement fork-join, but an idiomatic golang implementation won't stop you from forking and forgetting to join.
Another aspect of it is not really technical, but conventions that fell out of what the language offers. It's just way more common to DIY something custom from a couple of channels, even if it could be done with some pre-defined standard pattern. To me, this makes understanding behavior of golang programs harder, because instead of seeing something I already know, like list.par_iter().map().collect(), I need to recognize such behavior across a larger block of code, and think twice whether each channel-goroutine dance properly handles cancellations, thread pool limits, recursive dependencies, is everything is correctly read-only/atomic/locked, and so on.
Channels communicating between persistent workers are fine when you need decoupled asynchronous operation like that. However, channels and detached coroutines are less appropriate in a bunch of other situations, like fork-join, data parallelism, cancellation of task trees, etc. You can still do it, but you're responsible for adding that structure, and ensuring you don't forget to wait for something, don't forget to cancel something.