4 ms·
> You just use goroutines, IO transparently happens on a dedicated threadpool, CPU heavy tasks are spread out over the available cores. In Rust you can do that
by pcwalton 7y ago
> You just use goroutines, IO transparently happens on a dedicated threadpool, CPU heavy tasks are spread out over the available cores.
In Rust you can do that too! Just use threads. That's what Go does: it just uses threads.
Async I/O is there if you need better performance than what Rust—or Go—can provide. If you don't need that performance, then don't use async I/O.
- foldr 7y agoGo uses async IO under the hood (for network IO at least). The exposed API is blocking, but the underlying implementation uses async IO and transparently schedules Goroutines across different OS threads. As you know, Rust doesn't currently have stable support for a similarly high-level interface to async IO. See e.g. the following reddit thread from 3 months ago: https://www.reddit.com/r/rust/comments/ausuld/what_is_the_current_state_of_async_io_in_rust/ https://www.reddit.com/r/rust/comments/ausuld/what_is_the_cu...
- pcwalton 7y agoUsing async I/O in userspace to implement the sync I/O interface of goroutines is equivalent to what the kernel does to implement sync I/O for normal threads.
- foldr 7y agoKind of, but with the difference that Goroutines are lighter than kernel threads, so you can run more of them.
- pcwalton 7y agoNot as much as you'd think. You can run thousands of kernel threads on Linux.
- foldr 7y agohttps://golang.org/doc/faq#goroutines https://golang.org/doc/faq#goroutines "It is practical to create hundreds of thousands of goroutines in the same address space. If goroutines were just threads, system resources would run out at a much smaller number." More generally, Go is hardly the first language to implement userspace threads backed by OS threads. Erlang famously does this, for example, and so does GHC: https://en.wikibooks.org/wiki/Haskell/Concurrency https://en.wikibooks.org/wiki/Haskell/Concurrency In response to your other comment, the whole point of Goroutines is that you can use them pretty freely without (usually) having to worry about how many there are. Let's say you're writing a server, and that handling one of the requests requires completing 10 tasks, each of which can run in parallel. In Go you can just spawn a Goroutine for each of those tasks. But if you were using real OS threads, then that implementation could easily end up spawning more than a few thousand threads, when the server was under significant load.
- pcwalton 7y agoThat FAQ answer is misleading. Linux supports thousands of threads just fine. If you have hundreds of thousands, you probably want true async I/O instead of having stackful threads at all. > But if you were using real OS threads, then that implementation could easily end up spawning more than a few thousand threads, when the server was under significant load. There's no inherent problem with spawning a few thousand threads in Linux.
- foldr 7y agoThe FAQ is talking about “hundreds of thousands of threads”. It doesn’t make sense to respond by saying that Linux is fine with “a few thousand threads”. Similarly, I was talking about a scenario where a server would end up spawning more than a few thousand threads. >If you have hundreds of thousands, you probably want true async I/O instead of having stackful threads at all. This is a completely baseless assertion, and really makes no sense at all. "Async IO" in itself doesn’t even offer a means of exploiting multiple CPU cores. If "async IO" were all that was required, people would be sticking with Node.js for heavy loads. >even 2kB of stack per thread is 195MB just from thread stacks. You say this as if 195MB were a large amount of memory for a server to be using! Obviously, if the memory overhead of running 100,000 Goroutines is only 195MB, then memory limitations are not going to prevent you from running 100,000 Goroutines.
- the_duke 7y agoSure, technically there is little difference, but my point was is is much easier to use for the developer. The complexity is hidden from you and the choices are largely already made by the runtime. Sure, modern Linux handles 10k threads just fine, but where Go shines are middle-level network services or microservices where you can easily have 10+ additional tasks for each incoming request. Using real threads quickly becomes unfeasible, and also more cumbersome (passing around threadpools, which threadpool do I use for this task?, do I really want to do those two things in parallel and eat up an extra thread? the cancellation issue, ....). Also in Rust you'd need to rewrite a lot of code when switching from sync to async, and still need to delegate things to a threadpool since not every dependency will be async enabled. When the ecosystem stabilizes Rust will be a great choice for the lower stack libraries and servers, where the performance matters. But for many projects the increased complexity, for moderate performance gains, would be a hard sell.
- pcwalton 7y agoDo many Go programs really have 100,000 goroutines simultaneously? I'm certain that's quite rare.
- saturn_vk 7y agoWell, even if you do not do any parallel work for each request in your service, each request in itself is a goroutine. So depending on the load of that service, you might reach such numbers, and more.
- stickupkid 7y agoWorking as part of Juju project, I can tell you for certain that the answer here is yes, very much so.
- pcwalton 7y agoIf you have over 100,000 threads, then you are at the point of scalability where you probably want more than what Go can provide. Async I/O would likely be better for your use case, because even 2kB of stack per thread is 195MB just from thread stacks.