The concurrency and threading in Go just feels like magic compared to every other language. I'm a goroutine addict and I refuse to be rehabilitated.
Just from observations over the years, I don't think there's any other language quite like this, in terms of how things can end up happening in any thread.
Managing channels and making sure they are closed just once is quite messy compared to other languages. The Go channel axioms[1] don't make much sense: why does closing a channel multiple times panic, but reading from a closed channel returns a zero value?
Kotlin gets this right. On send/receive, you can use trySend or tryReceive if you want to avoid exceptions. Considering Kotlin also has coroutines and structured concurrency, concurrency in Kotlin feels more ergonomic to me than Go. At least if you want to get concurrent code with least amount of bugs and not just least amount of extra keywords.
> why does closing a channel multiple times panic, but reading from a closed channel returns a zero value?
Not only that, but writing to a closed channel also panics. You need to close it exactly once from the sender side, and then somehow differentiate on the receiving side between an explicit zero value being sent, the channel being empty but not closed, and the channel being empty but not closed. It's not clear to me how this is possible without either using another channel (and then basically repeat the same problem on that new channel) or use some sort of shared memory like an atomic bool, at which you're no longer purely message passing.
I don't have any qualms with shared atomic primitives for synchronizing concurrency, but it's kind of weird that everyone talks so much about goroutines and channels when channels have such a weird design. Needing to use a separate mechanism to circumvent completely avoidable design issues for anything more complex than "never close the channel" does not seem particularly praiseworthy to me.
The easiest way to deal with receiving from a channel is using range over it in a separate goroutine. The for range finishes only when the channel was closed and all values were read.
Of course, that won't work if you want to receive from several channels in the same goroutine. For that you can use select with receive assigning to two values and the second one is set to false if channel is closed.
So, I never really had issues on receive side, I agree with the send side, though. The solution I used when sending from multiple goroutines is to wait for the senders to be finished in the "main" goroutine and only the close the channels.
But yeah, it does require some thought to be put into how this is all organized.
> The solution I used when sending from multiple goroutines is to wait for the senders to be finished in the "main" goroutine and only the close the channels.
How do you know when they're finished though? It seems like you're need to have an additional channel or atomic boolean per goroutine for this, which just increases the amount of organizational burden.
SamInTheShell · · focus · HN ↗
Just from observations over the years, I don't think there's any other language quite like this, in terms of how things can end up happening in any thread.
chimbambum · · focus · HN ↗
unscaled · · focus · HN ↗
Kotlin gets this right. On send/receive, you can use trySend or tryReceive if you want to avoid exceptions. Considering Kotlin also has coroutines and structured concurrency, concurrency in Kotlin feels more ergonomic to me than Go. At least if you want to get concurrent code with least amount of bugs and not just least amount of extra keywords.
[1] <a href="https://dave.cheney.net/2014/03/19/channel-axioms" rel="nofollow">https://dave.cheney.net/2014/03/19/channel-axioms
saghm · · focus · HN ↗
Not only that, but writing to a closed channel also panics. You need to close it exactly once from the sender side, and then somehow differentiate on the receiving side between an explicit zero value being sent, the channel being empty but not closed, and the channel being empty but not closed. It's not clear to me how this is possible without either using another channel (and then basically repeat the same problem on that new channel) or use some sort of shared memory like an atomic bool, at which you're no longer purely message passing.
I don't have any qualms with shared atomic primitives for synchronizing concurrency, but it's kind of weird that everyone talks so much about goroutines and channels when channels have such a weird design. Needing to use a separate mechanism to circumvent completely avoidable design issues for anything more complex than "never close the channel" does not seem particularly praiseworthy to me.
prerok · · focus · HN ↗
Of course, that won't work if you want to receive from several channels in the same goroutine. For that you can use select with receive assigning to two values and the second one is set to false if channel is closed.
So, I never really had issues on receive side, I agree with the send side, though. The solution I used when sending from multiple goroutines is to wait for the senders to be finished in the "main" goroutine and only the close the channels.
But yeah, it does require some thought to be put into how this is all organized.
saghm · · focus · HN ↗
How do you know when they're finished though? It seems like you're need to have an additional channel or atomic boolean per goroutine for this, which just increases the amount of organizational burden.
prerok · · focus · HN ↗