Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Go channels are not really asynchronous. On the sender side they block until the buffer has room. This can lead to deadlocks.

Erlang processes have very small stacks just like goroutines do.



They can be. By default a `make(chan int)` is blocking, but you can give it a buffer size with `make(chan int, 1000)` or something and it won't block until it's full.


They won't block until it's full. That's still blocking, and you can still get deadlocks from it.


When this happens, it seems like it tends to be exposing a fault in the design of the program. In a purely asynchronous system you can obviously avoid deadlocking in interprocess communication while still having a system that never correctly converges.


Sure, a program that uses a channel with a large buffer size as if it were an asynchronous channel contains a bad bug. The point is that if you need such an asynchronous channel, Go doesn't provide it. There are many possible examples of programs that need truly asynchronous channel functionality in which using a channel with a large buffer size would expose the program to subtle deadlocks that may only manifest in the wild on large data sets.


Wouldn't a correct design for programs that occasionally needed to handle huge data sets be to consciously and deliberately serialize (or at least bound the concurrency of) some parts of the code, rather than to pretend that the program was operating on an abstraction that could buffer arbitrary amounts of data in parallel and always converge properly?


Yes. You can always build the right thing in a system with synchronous channels—there are many ways to build asynchronous channels out of synchronous channels, after all, even if they aren't built into the language. My point is that asynchronous channels make it easier to avoid accidentally shooting yourself in the foot, that's all.

For what it's worth, I don't think that Go made a bad decision here (although I personally wouldn't have made the same decision, because of examples like those I gave in my other reply downthread). Certainly synchronous channels are faster. There are always tradeoffs.


If you want async, you could really just keep consuming from the channel with goroutines until your hardware catches on fire. There's nothing in Go that is limiting this behavior.


Async communication makes it harder, but doesn't avoid it by default. E.g. if in in Erlang gen_server A calls gen_server B w/o timeout and B, to process this call, then calls A (ouch, bad design, but possible), you've got a wonderful deadlock.

I'm using both, Erlang/OTP and Golang. Both have their strengths and advantages. So simply use the right tool for the right work (and no hammer to turn a screw).


Right, you can deadlock in any actor-based system (well, not any actor-based system—I've seen research systems that provably do not, but they're research). Async communication just makes it harder to screw up, as you say.


Of course. I'm not saying that they're 100% async, just defending the fact that they're not 100% blocking.


My point is that there are actually very few concurrency problems where deadlocks are solved by increasing the buffer size by some fixed amount. If you want your code to be correct, in most cases a buffer size of 100 might as well be a buffer size of 1, except that an increased buffer size can improve performance for some scenarios.

When people talk about asynchronous channels, they usually mean that you can stream messages to another actor and know that you won't block. That is not true for Go channels. You can increase the buffer size, but that just reduces the chance that your program will deadlock: it doesn't make a Go channel work in situations where you need an asynchronous channel for your program to be correct.


You can always do something like

    go func() { ch <- foo }()
then your normal program flow won't block. I guess that's what you mean by deadlock. If your program runs into an actual deadlock, the runtime will detect it, crash the program and show stacktraces.


What about the "select" statement for it's remedy?


That just gives you more complicated deadlocks. You still have to be aware of the potential for tasks to deadlock and consciously design them not to do that. It's a problem that does come up all the time in Go programs, but tends to come up quickly enough (due to the way concurrency is designed in Golang) that you fix it quickly, like an accidental nil reference in a Ruby program.




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: