regularfry 8 days ago

This was 2016. Is it all still true? I know things will be backwards compatible, but I haven't kept track of what else has made it into the toolbox since then.

4
sapiogram 8 days ago

Absolutely nothing has changed at the language level, and for using channels and the `go` keyword directly, there isn't really tooling to help either.

Most experienced Golang practitioners have reached the same conclusions as this blog post: Just don't use channels, even for problems that look simple. I used Go professionally for two years, and it's by far the worst thing about the language. The number of footguns is astounding.

pdimitar 5 days ago

Okay, but what do you use then?

fpoling 8 days ago

The only thing that changed was Context and its support in networking and other libraries to do asynchronous cancellation. It made managing network connections with channels somewhat easier.

But in general the conclusion still stands. Channels brings unnecessarily complexity. In practice message passing with one queue per goroutine and support for priority message delivery (which one cannot implement with channels) gives better designs with less issues.

NBJack 8 days ago

My hot take on context is that it's secretly an anti-pattern used only because of resistance to thread locals. While I understand the desire to avoid spooky action at a distance, the fact that I have to include it in every function signature I could possibly use it in is just a bit exhausting. Given I could inadvertently spin up a new one at will also makes me a bit uneasy.

fpoling 8 days ago

One of the often mentioned advantages of Go thread model is that it does not color functions allowing any code to start a goroutine. But with Context needed for any code that can block that advantage is lost with ctx argument being the color.

dfawcus 8 days ago

Context is not need for any code which can block.

Context is only needed where one has a dynamic graph where one wishes to cleanly tear down that graph. One can operate blocking tx/rx within a fixed graph without the need for any Context to be present.

Possibly folks are thinking of this only for "web services" type of things, where everying is being exchanged over an HTTP/HTTPS request.

However channels / CSP can be used in much wider problem realms.

The real gain I found from the CSP / Actor model approach to things is the ability to reason through the problems, and the ability to reason through the bugs.

What I found with CSP is that I accept the possibility of accidentally introducing potential deadlocks (where there is a dependancy loop in messaging) but gain the ability to reason about state / data evolution. As opposed to the "multithreaded" access to manipulate data (albeit with locks/mutexes) where one can not obviously reason through how a change occurred, and its timing.

For the bugs which occur in the two approaches, I found the deadlock with incorrect CSP to be a lot easier to debug (and fix) vs the unknown calling thread which happened to manipulate a piece of (mutex protected) shared state.

This arises from the Actor like approach of a data change only occurring in the context of the one thread.

fpoling 8 days ago

Actor model does not need CSP. Erlang uses single message passing queue per thread (process in Erlang terminology) with unbounded buffer so the sender never blocks.

As the article pointed out lack of support for unbounded channels complicates reasoning about absence of deadlocks. For example, they are not possible at all with Erlang model.

Of cause unbounded message queues have own drawbacks, but then Erlang supports safe killing of threads from other threads so with Erlang a supervisor thread can send health pings periodically and kill the unresponsive thread.

Mawr 8 days ago

Function arguments do not color functions. If you'd like to call a function that takes a Context argument without it, just pass in a context.Background(). It's a non-issue.

Full rebuttal by jerf here: https://news.ycombinator.com/item?id=39317648

eptcyka 8 days ago

Coloring is a non-issue, context itself is. Newcomers find it unintuitive, and implementing a cancellable piece of work is incredibly cumbersome. One can only cancel a read/write to a channel, implementing cancellation implies a 3 line increase to every channel operation, cancelling blocking I/O defers to the catchphrase that utilises the name of the language itself.

fpoling 8 days ago

Cancelling blocking IO is such a pain point in Go indeed. One can do it via careful Close() calls from another goroutine while ensuring that close is called only once. But Go provides absolutely no help for that in the standard library.

dfawcus 7 days ago

I had a need for that, on TCP and UDP connections. The former behind a net.Conn.

Rather than fiddle with Close() calls, from memory what I found worked was setting a short (or in the past) deadline on the Conn (SetReadDeadline). This as it is documented as applying even to an existing blocked call, and makes that blocked call eventually return an error.

So when I wanted to tear down the TCP connection and clean up the various goroutines associated with using the connection, I'd unblock any blocking i/o, then eventually one had an easy Close() call on the connection.

UDP was simpler, as each i/o operation had a short timeout. I've not investigated what could be done for pipe and fifo i/o.

int_19h 7 days ago

By the same token, being async doesn't color a function since you can always blocking-wait on the returned promise. But then the callers of that function can't use it async.

Similarly with Context, if your function calls other functions with Context but always passes in Background(), you deprive your callers of the ability to provide their own Context, which is kinda important. So in practice you still end up adding that argument throughout the entire call hierarchy all the way up to the point where the context is no longer relevant.

athoscouto 8 days ago

Yes. See update 2 FTA for a 2019 study on go concurrency bugs. Most go devs that I know consider using higher level synchronization mechanisms the right way to go (pun intended). sync.WaitGroup and errgroup are two common used options.

mort96 8 days ago

Channels haven't really changed since then, unless there was some significant evolution between 2016 and ~2018 that I don't know about. 2025 Go code that uses channels looks very similar to 2018 Go code that uses channels.

regularfry 8 days ago

I'm also wondering about the internals though. There are a couple of places that GC and the hypothetical sufficiently-smart-compiler are called out in the article where you could think there might be improvements possible without breaking existing code.