Hacker News new | ask | show | jobs
by philwelch 2218 days ago
> If the task that bar() will return is created when you first call it, then you're right, we didn't gain much. However, the task may have already been running for a long time behind the scenes, we may have done things in parallel with that run, and now that we need the result, we can block.

So in Go that’s just a blocking channel receive.

2 comments

Not exactly. Async allows for N tasks of linear code execution on the same thread at the cost of managing cooperative yields.

Go and channels allow you to block many threads with little overhead and little syntax but at the cost of not being able to easily target a single thread without manual orchestration through channels.

Consider a scatter/gather algorithm. N async tasks, N coroutines. Start N from the main thread and block until all complete. Simple right?

Its not!

Imagine there are GPU commands that must be grouped or even logs where you want some of the subroutine's logs to be grouped.

With async/await you have the verbosity to synchronize tasks to thread contexts such that you know chunks will not be run in parallel. Its easy to control when execution leaves the current thread. It can ensure that it both does or does not yield execution. You can easily switch between synchronized main thread execution and parallel chunks without any top down design.

With goroutines you most likely would write the ordered results to a channel that was passed around. I'm not sure if channels support N inserts in an atomic fashion out of the box but if not it must be a channel of arrays or maybe some kind of control channel as well. Hopefully you have access to every piece of code you need to synchronize. This all assumes you can get away with a single main goroutine. If you need a single special OS thread for interop I'm sure its more complex. Its not just a blocking channel.

Essentially the two paradigms can do everything but they both seem to excel in certain cases.

Sure, it is, but now you're moving the goal posts. The initial assertion was that `var x = await Bar()` is equivalent to `x := Bar()` in Go, and I was explaining that it is not.

Yes, `await Bar()` is equivalent to ` <- ch`, but only if there is some goroutine that actually writes something to the channel. So there is no getting around the fact that there are colored functions to the same extent in Go as in C#/Java, in regards to asynchronicity.

What’s unclear and magical to me is the notion that Bar() is returning the result of a computation that was potentially started a long time ago. In reality, “await Bar()” could be a clumsy attempt to force a needlessly async function to behave synchronously, or it could be simply receiving a result from some other task that’s been running behind the scenes. So you’re shifting the goalposts a little bit, too—awaiting Bar() is only equivalent to a channel receive if there is some other task that actually started the work.

And I maintain that Go does not have colored functions. Colored functions are when you hijack function call semantics to do asynchronous programming. Go doesn’t do this; the asynchronous behavior has to be done explicitly. Even though it’s possible, it isn’t idiomatic for a function to return a channel that you have to try and receive later, the way async functions have to be awaited before you actually get the return value.

> So you’re shifting the goalposts a little bit, too—awaiting Bar() is only equivalent to a channel receive if there is some other task that actually started the work.

The most correct way of looking at this is that async fucntions return a channel that you can read data from, and await is exactly like a channel read. The magic in async/await comes in the function calling 'await', which is suspended and scheduled to continue later. So, just like in Go, you could create a function that needlessly uses channels but is otherwise synchronous, or you can have a function that uses channels because it actually is doing something in the background.

> Colored functions are when you hijack function call semantics to do asynchronous programming. Go doesn’t do this; the asynchronous behavior has to be done explicitly.

Colored functions are a very general concept. They refer to families of functions which perform the same computation but that need to be written differently for syntactic reasons. Famous examples that have nothing to do with async include C++ const-ness, where you have to write a const and a non-const version of a function to to be able to offer the same functionality for this and for const this.

Go has this with functions which return data versus functions that push data on a channel. If you want your function to be callable in a sync manner, it must return the data directly. If you want it to be callable in an async manner, it must take a channel and push its result on that channel. You can't launch a sync function as a new goroutine and get the result back. You can't call an async function as a regular function and read the result back. So, they are two colors of functions in Go.

More concretely, say you are writing an HTTP client lib. If you want to allow your users to start multiple requests in parallel in a nice fashion, you must write your request function to take a channel and push its response there. If you want to make it easy for your users to just call your function in a sync manner, you must write a function that returns the response as its return value.

Of course, you can implement one in terms of the other, as demonstrated earlier. But you still need to add that wrapping, so your functions have a color.