It sounds like Go didn't follow the suggestion of pretty much every CS books on concurrency: favor containers over concurrency primitives.
> while Go forces channels for `select` whether they model your problem nicely or not, and they're very difficult (often impossible) to wrap without changing semantics.
I understand that Go's concurrency model is based on CSP and fork-joins and the primitives like locks, but they are not mutually exclusive with concurrency containers, right? It's okay if the Go team's core philosophy is that channels are the universal abstraction, but I don't get why the community didn't produce 3rd-party containers as robust as JCTools.
No, they're not mutually exclusive and tons of go code uses both to implement some kind of semantic. They serve moderately different purposes because their semantics are quite different.
Select, however, is encouraged very widely, and is used very widely, because it's very useful to be able to wait on any of multiple things efficiently. Select is great, you quickly grow to miss it in languages that don't have it. But select only works with channels, and often that means you're essentially forced to use channels where a mutex is much simpler and more natural. It also means tons of APIs are channel-centric because it's kinda the only non-blocking option (atomics exist, but that's a very different kind of non-blocking, and dramatically more error-prone for normal humans to write).
Once you go channel-centric for things like streams or concurrent RX style stuff, it does work, and some libraries do exactly that. But then you run into the historical limitations on generics, which makes for sometimes unnatural code, and channel performance is usually significantly worse than mutexes (it's still very fast, but you don't want to use it for extremely small things). And you are pretty much required to use those channels directly with select by hand because wrapping channels changes some semantics. And the semantics of those libraries are not mutex-y and are different from what most are already used to, because few other languages are channel-centric.
So you end up with high-level channel-oriented concurrency libraries that people only want to use for large expensive operations, thus are only designed for large expensive operations, which means it's not very common over all, and few develop the habit. E.g. there are quite a lot of goroutine-pool-helping libraries for ~seconds of work, but few functional-flavored generic parallelizing ones for opportunistic use.
It's part ecosystem, part language design, and part language history. You can do most of the Java stuff in Go with enough effort, but it just isn't done in practice very much, and it'll look and feel quite different.
> while Go forces channels for `select` whether they model your problem nicely or not, and they're very difficult (often impossible) to wrap without changing semantics.
I understand that Go's concurrency model is based on CSP and fork-joins and the primitives like locks, but they are not mutually exclusive with concurrency containers, right? It's okay if the Go team's core philosophy is that channels are the universal abstraction, but I don't get why the community didn't produce 3rd-party containers as robust as JCTools.