10 ms·
“Go is to services what Rust is to systems.”
by sjroot 7y ago
“Go is to services what Rust is to systems.”
- sime2009 7y agoWould it be fair to say that Go's concurrency support makes it a good match in the network services space? I see Go more often in Docker and cloud contexts.
- pcwalton 7y agoRust has broadly the same concurrency support, with a more powerful compile-time race detector (but one that also comes with a learning curve). The main difference is that Go uses M:N threading, while Rust uses 1:1 threading with optional explicit async/await constructs.
- sime2009 7y agoFor the threading difference, do you mean that Rust needs 'normal' threads plus async/await support, but Go can just lean on its built in concurrency support for both cases? i.e. Go threads instead of explicit async style code.
- littlestymaar 7y agoYour sentence implies that Go green threads can do both of what 1:1 threading and async/await can, but it's more complex than that: there's a tradeoff between the simplicity (only one concurrency primitive) and the capability of the said primitive: - with it's M:N model, Go cannot really do FFI efficiently, while both 1:1 async/await have no problem with that in Rust. - goroutines are cooperatively scheduled, while OS thread are preemptively scheduled. If you have hot loops, Go's model won't be able yo guarantee fairness between goroutines, which may be a problem depending on your usage. OS threads don't have this problem. - with goroutines you won't achieve the level of performance you can reach with async/await. Go and Rust fills a different niche of programming and take a different stand on this tradeoff: Rust gives you more power at a complexity cost. Go offers you more simplicity, with less power.
- kjksf 7y agoThe reason cgo is (relatively) slow is small stacks, not M:N threading. Small stacks (and M:N threading) are needed to efficiently implement tens of thousands of goroutines. CGO is slow mostly because it needs to switch to a larger stack when calling a C function. It's a trade-off. A different Go implementation could make Goroutines map 1:1 to threads and have fast cgo calls at the expense of slow goroutines. Rust is not exempt from those trade-offs. They chose fast FFI and smaller runtime. They paid with slow threads. Async/await promises to be the best of both worlds but it comes at a cost of great complexity, both for the programmer and the implementor. At the end of the day under the covers it's just threads that need to be managed in a complex and often invisible way plus a complex rewrite of your straightforward code into a mess of a state machine. I can confidently say that learning to use goroutines took 10x less time than learning async/await in C#. I understand goroutines better than I ever understood async/away.
- pcwalton 7y ago> Small stacks (and M:N threading) are needed to efficiently implement tens of thousands of goroutines. Tens of thousands of threads are no problem with 1:1 threading on Linux. > They paid with slow threads. I would not call 1:1 threads "slow threads". If they were slow, then the 1:1 NPTL would have not defeated the M:N NGPT back in the day when this was being debated in the open source OS community. > complex rewrite of your straightforward code into a mess of a state machine. The entire point of async/await is that you don't have to do a complex rewrite of your straightforward code. It remains straightforward, and the compiler does the transformation for you.
- ngrilly 7y agoRust is really great, but I feel like you are overselling it a bit here: - async/await is available in the nightly version of Rust (1.39), but not in the stable version (1.37). - 1:1 threading is not strictly equivalent to M:N threading (the main practical difference being that the stack size per OS thread is larger than the stack size per goroutine). Writing that "Rust has broadly the same concurrency support" is misleading right now. But it could be true in a near future.
- pcwalton 7y agoThe features that each language offers are the same: threads, channels, and blocking I/O (though Rust has more features for safe concurrency--Rust prevents data races statically, while Go is not even memory safe in the presence of such races). What is different is the performance characteristics. In some cases, M:N will be more efficient; in some cases, 1:1 will be. But just as I wouldn't say Go is lacking FFI features because M:N makes cgo slow, I wouldn't say Rust is lacking concurrency features.
- ngrilly 7y ago> blocking I/O Rust I/O are strictly similar to Go when using 1:1 OS threading, but become conceptually different when using async/await. > Rust has more features for safe concurrency--Rust prevents data races statically, while Go is not even memory safe in the presence of such races Agreed. That's a big advantage of Rust. > What is different is the performance characteristics. Performance is a feature. It's significant enough to justify using one language or the other depending on the project at hand. > In some cases, M:N will be more efficient; in some cases, 1:1 will be. The main advantage of the M:N model, compared to the 1:1 model, is the memory usage, because each goroutine starts with a small stack (a few kB). It makes possible to start a larger number of M:N goroutines than 1:1 threads. > But just as I wouldn't say Go is lacking FFI features because M:N makes cgo slow, I wouldn't say Rust is lacking concurrency features. Go's FFI works but is slow. It's a well known fact. Rust's concurrency story is not stabilized yet (areweasyncyet.rs). It's a fact too. I don't see a problem with acknowledging both :)
- 7y ago
- Groxx 7y agoPersonal opinion: no. Go's concurrency is hard to control or monitor, and it's hard to make higher level abstractions that are both safe and convenient. It's of course possible, but the tradeoffs are rather severe. Go's concurrency shines best in short-lived single-purpose processes, which is a near perfect fit for CLI tools. When you don't care if something gets abandoned or fails to make progress and can just ctrl-c, the relative simplicity is 100% worth it. Other parts of Go work well here too, especially the very low startup overhead (unlike, say, python). For long lived processes though, like most services, it feels very error-prone or very boilerplatey and manual.
- bborud 7y agoCould you expand on what you think is hard about the concurrency in Go? (And perhaps give an example of a language that makes it easy/easier?)
- gen220 7y agoNot the OP, but if you’ve ever had to write 100% fault tolerant concurrent Go code, you find yourself having to add a lot of monitor-recover-restart “boilerplate” that ends up being quite complex, depending on the task at hand. It helps if you follow certain principles like making your concurrent tasks idempotent etc., but Go doesn’t force you to write things that way, so it takes a bit of remembering each time. If the task is relatively simple, you end up writing a lot of concurrency infra for a little bit of concurrency, which can become frustrating if you’re doing it for the Nth time. It sounds like a problem that’s ripe for abstraction, but it turns out that it’s quite hard to build abstractions that get all the trade offs just right for each case. There’s some worker pool style libraries out there, but if you care about performance you can usually eke more out by writing a custom solution. As for alternatives, I have heard that erlang has a better story for this, because of the inherent restrictions of the language, but I haven’t used it much. In my experience, Go gives you enough rope to make this task “merely annoying” (rather than impossible or astoundingly challenging) without giving you enough to truly hang yourself with. You can get really tangled up though :)
- masklinn 7y ago
- sagichmal 7y agoYes, Go is in many ways a DSL for writing servers. IMO it should be the default language you reach for when starting a new server project.
- therockhead 7y agoDo you include web development in that ?
- sagichmal 7y agoRails-style, ORM-centered, frontend+backend web development? No. Something different than that? Yes.
- littlestymaar 7y agoWithout cargo though
- ainar-g 7y agoGenuine question: What does Cargo do that Go 1.11 modules can't?
- littlestymaar 7y agoApplying the bug fixes in a dependency I'm using when a new version of it is published… [1] Sarcasm apart, I wasn't talking about features. But cargo is a really well-designed package manager. I've been using Rust full-time for 2 years now and it had never annoyed even once. [1] the sarcasm was referring to the Minimum Version Selection algorithm used by go's package manager, which is a really bad case of NIH from Go's teams who decided to throw away a thriving community work to do their own stuff, ending up with something different from what everybody else has been for more than 20 years. And unsurprisingly, it's really bad…
- tapirl 7y ago> Applying the bug fixes in a dependency I'm using when a new version of it is published… You can replace a dependency with a local modified copy of the dependency to do this. Minimum Version Selection is for security reason. It might has some disadvantages, but personally I like it. It gives programmers more control.