The main culprit to me in Golang seems to be channels, not goroutines. If your workflow essentially is defined by a mesh of channels and goroutines, it's hard to reason or understand. I have no direct practical knowledge of Golang, but working on a large application that used BlockingQueue for concurrent communication and one which extensively used services buses for communication - both were hard to understand and r…
I think the article's point is that with Future's you can still pretty easily invoke a Future-returning function and forget to return its value, ending up with what you might call an orphan continuation.
Notes on structured concurrency, or: Go statement considered harmful
121–130 of 234 posts
Re: Notes on structured concurrency, or: Go statement considered harmful
#122Earlier quoted context omitted.
+1. This is structured programming, applied to concurrent setting. The argument for structured programming was made and won in the '60s [0]. It is amazing that we keep making the same mistakes over and over again. > The unbridled use of the go to statement has an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress. Usually, people take…
> The argument for structured programming was made and won in the '60s Most code out there is fine using break and continue in loops, and early returns are pretty popular. All of these are unstructured programming (by the 60s definition). I don't think it's all that clear that it has won.
Re: Notes on structured concurrency, or: Go statement considered harmful
#123This addresses the wrong problem. The real issue is control over data shared between threads, not control flow. C/POSIX type threads have no language support for indicating what data is shared and which locks protect which data. That's a common cause of trouble. The big question in shared memory concurrency is "who locks what". Most of the bugs in concurrent programs come from ambiguities over that question. Early at…
Re: Notes on structured concurrency, or: Go statement considered harmful
#124Earlier quoted context omitted.
> The built in thread pools must have a limit. The go runtime has been shown to handle millions of goroutines, and there are performant and scalable programs, like Cloudflare's RRDNS that run tens of thousands of goroutines ( https://blog.cloudflare.com/quick-and-dirty-annotations-for-... ). There's plenty of web software using this model written in Go. I haven't done UI programming in Go - but in your example, I'd i…
> The go runtime has been shown to handle millions of goroutines, Fair enough. > I haven't done UI programming in Go - but in your example, I'd imagine you could simply have an event launch a goroutine... But now you are in the "goto" mess that this article describes. Async/await allows me to to write "goto safe" async code synchronously, for UIs.
This is where I don't really agree with the article. I agree with you that async/await may be better for UI programming - Go, and Erlang, which has a similar runtime to Go, aren't used to make frontends.
The article presents this example of 1 mainthread spawning 3 shortlived parallel tasks, waiting for them to complete, before they continue. This he argues, creates spaghetti code, where the concerns of a single routine are split among 3 different functions. This is the spawn/join model, and while it exists in Go, this isn't how concurrent go programs are normally written.
Goroutines are more commonly used like actors - in that a single goroutinThe concept of "branching off or onto" the main thread doesn't really exist. Instead my main thread runs something like an event loop, and in your example, the OnButtonClick handles the event synchronously, in its own goroutine, and will send a message back to the main thread's event loop on whatever state needs to change. This is the idea behind the CSP/Actor model which has been proven to scale for years - Erlang/OTP is a major proponent of it and is over 30 years old, and proven to scale (ex. Whatsapp managed 900M uses with only 50 engineers). If this model was as bad as goto, I don't think Erlang would have the reputation for building concurrent & parallel software it has today. This Actor model also isnt something that I can easily grok into the Trio library - and I'm hesitant to call something like Trio superior when the Actor model has years of experience.
Each model has their own pros and cons. A major plus of the Go model vs async/await is that I don't have to think about writing "asynchronous" code. I can write my code any way I like, and the Goruntime can easily make it asynchronous (partly because the Go language has already done the hard parts - everything, like sockets and files are async by default). This isn't true of the major async/await languages - in Javascript, everything that might be blocking has to either use callabacks or the async keyword. In Python, if I use a library that doesn't use my async library or is synchronous I lose. And Rust, it's already starting to rear its head as if I'm writing a library using Tokio, and I want to include a library using Rayon, I'm going to have problems. You could see why the designers, who thought they were building a systems language, would be wary of exposing an async runtime. Async runtimes "infect" everything around it.
However, a major plus async/await model is that since everything is async, its very easy for very small functions to be completely non-blocking. In Go if you had set of serial functions, they would always execute serially. For example if a request came into a web server, it would get its own goroutine. Then that goroutine would read from redis and then mysql. Most gophers would write the code such that the read from mysql would only happen only after the read from redis. You could put both reads in a goroutine - but async/await is much more efficient here, requires much less lines of code, and wont require a mutex. In async/await the two database reads will almost always execute parallely, without the developer having to do anything.
Each model has its own strengths and weaknesses which is why it's hard to consider one strictly better for another.
Re: Notes on structured concurrency, or: Go statement considered harmful
#125Didn't expect to say this, but the article is completely right! This is obviously the right way to write concurrent programs. Kudos for writing this. One question though. The first part of the article says that "onclick" handlers should be replaced with nurseries as well. But I don't see how. Can someone explain?
I see fundamental issues with it: in some cases the checking model proposed by Rust is better; also - and this is related -, your don't always fix things reliably by mindlessly extending lifetimes or delaying things until termination of others, in the same way that mindlessly switching a resource usage to a shared_ptr in C++ if you had a lifetime issue can't be done in the general case, because you could very well only be trading a bug for another. Checking capabilities are more useful and general than constructive limitations, especially when we have load of counter examples on use cases.
So without hesitation: yes, this is more structured than having no structure on the point considered, but that is not at all a sufficient criteria to make that the kind of panacea the author seems to think it is. I would have been way more positive in seeing that presented as a comparison with the other existing solutions, similar or not, and without that little escape hatch story that makes me thing the author has found a hammer and now everything looks like a nail to them.
Re: Notes on structured concurrency, or: Go statement considered harmful
#126What. This article proposes a "nursery", which is just a wrapped sync.WaitGroup/pthread_join/futures::future::join_all/a reactor that waits for all tasks to terminate/etc. It then uses an exception-like model for error propagation to "solve" error handling (which is fairly easy to handle with a channel). The construct is a decently usable, already applied tool to handle a set of problems, but the article takes the is…
Re: Notes on structured concurrency, or: Go statement considered harmful
#127Didn't expect to say this, but the article is completely right! This is obviously the right way to write concurrent programs. Kudos for writing this. One question though. The first part of the article says that "onclick" handlers should be replaced with nurseries as well. But I don't see how. Can someone explain?
Re: Notes on structured concurrency, or: Go statement considered harmful
#128Earlier quoted context omitted.
In Go it doesn't really matter how many "threads" you have because Go has its lightweight threading model (aka coroutines) - creating a new "thread" is very cheap. The reason C#, Python (and Rust), have that model is because they don't have coroutines and starting another thread is very expensive.
Rust has co-routines on nightly ("generators"), and it's an important underlying aspect of async/await.
Re: Notes on structured concurrency, or: Go statement considered harmful
#129Earlier quoted context omitted.
Huh? I thought "select" was "ALT"??
'go' is PAR with different syntax (Everything after the 'go' line is implicitly one branch of the PAR) 'select' is ALT
'go' is more like a goto, the new goroutine is spawned, without any scoping. PAR opens a scope where all the contained routines are executed in parallel, but all of these must have terminated before the statement after the PAR is executed.
?
Re: Notes on structured concurrency, or: Go statement considered harmful
#130What. This article proposes a "nursery", which is just a wrapped sync.WaitGroup/pthread_join/futures::future::join_all/a reactor that waits for all tasks to terminate/etc. It then uses an exception-like model for error propagation to "solve" error handling (which is fairly easy to handle with a channel). The construct is a decently usable, already applied tool to handle a set of problems, but the article takes the is…
You've greatly underestimated how general this problem is.
First, Python's `with` statement has nothing to do with destructors. From the PEPM for `with`:
with VAR = EXPR:
BLOCK
which roughly translates into this:
VAR = EXPR
VAR.__enter__()
try:
BLOCK
finally:
VAR.__exit__()
`exit` is a method, not a destructor. As a result, Go could easily gain a `with`-like construct.Moreso, the issue applies even if there's nothing like `with` in the language. If I'm reading a function definition, and that definition uses the "acquire, try, finally, release" pattern that is the desugaring of `with`, then it sure would be nice to know that nothing from BLOCK is running after the `finally` statement has run. `go` breaks that assumption.