Hacker News new | past | comments | ask | show | jobs | submit login

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.




The built in thread pools must have a limit.

Also, this isn't just a matter of keeping thread resources down, it's supporting scenarios like developing web and UI software.

For example, if I have "OnButtonClick" and it does async work, I'd like to also call other functions that may also touch UI components. WPF has support for binding async actions to UI events, and it eliminates the need to ever wonder what thread you are on.

In short, you can't "join" a thread on the UI thread. Proper async/await with a task scheduler is invaluable.

Also, C# has thread pools. Most people don't create a raw thread using "new Thread()". They would normally use "Task.Factory" or "Task.Run". If it is a long running thread, you can specify to Task.Factory that is it long running and it will remove your thread from the thread pool and put another one in it's place.


>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 imagine you could simply have an event launch a goroutine - you could do whatever you want in that routine without blocking the main thread and simply pass a message back to the main thread if needed. Managing the number of goroutines isn't something you have to think about in go.

`Task.Factory`, and `Task.Run` still create threads, OS threads, which are different and much, much heavier than goroutines. You'd run out of memory trying to create 1000 OS threads, where as 1000 goroutines are a walk in the park (not just for Go, but for most runtimes using M:N threading).

async/await does have it's advantages over coroutine style concurrency, but I wouldn't count thread count as one of them.


> 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.


>But now you are in the "goto" mess that this article describes.

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.


Rust has co-routines on nightly ("generators"), and it's an important underlying aspect of async/await.


When I said coroutines, I really meant M:N thread scheduling. I could see how that can be confusing.


Yes, that's very different :)

That's Tokio in Rust.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: