4 ms·
Just got started with Rust and yeah, my biggest pain point so far were lifetimes and async code. I finally replaced tokio with multithreaded blocking code, whic
by ThePhysicist 4y ago
Just got started with Rust and yeah, my biggest pain point so far were lifetimes and async code. I finally replaced tokio with multithreaded blocking code, which seems to be much simpler and more familiar. The problem with async was that some of the libraries I needed (e.g. for QUIC) didn't support it, so you had an unholy mixture of blocking and non-blocking code.
Coming from a C++ background I appreciate Rust's novel approach of borrow checking, though I'm not sure if it's more "elegant" than C++'s approach with unique pointers, move constructors etc.. What I love is the modern ecosystem and build tooling around the language (including documentation and package management), for me that's Rust's main advantage over C++.
- mikojan 4y agoI've successfully replaced tokio with the lower-level mio in one such case. After all threads and async serve different purposes: Threads when you're computing a lot, async when you're waiting a lot, no?
- deleted 4y ago[deleted]
- lumost 4y agoThreads work OK for concurrency in many apps. The developer cost of async may not be worth it in many cases.
- ibraheemdev 4y agoI would say that threads work _very well_ for concurrency in _most_ apps. Thread context switches have gotten much much cheaper over the years, and the idea that threads are heavyweight is very outdated.
- lumost 4y agoTrue, java apps have comfortably run 10s of thousands of threads without issue historically. Coroutines are nice as they allow trivially writing an app which scales to millions of concurrent operations. Rust is nice as it lets us write code for every occasion, async is awful as it poisons the entire ecosystem. The same result could have been achieved with a few tactically inserted yield statements for I/O and locking which are pretty much the only times that async or coroutines help.
- 411111111111111 4y agoI'm just an average web developer, so I could be wrong here, but that's not my understanding of it. Your application has n threads to begin with, if you use async, these will be utilized to schedule tasks. You're basically trusting your language to properly pause and restart the procedures. If you spawn new threads, these will be managed by your kernel. If that kernel is Linux, that it already has a pretty good algorithm to pause and restart them, so you're not really wasting a lot of resources. So I believe there are effectively two differences: threads can scale up to the limit of your os/kernel and hardware. Async can scale up to the level of the threads your language spawns to handle these tasks. Threads trust the kernel/OS to prioritize workloads, async trusts the language
- jhugo 4y agoThe important difference is in purpose, not implementation. Most async runtimes use a thread or more per core, and the OS is doing that thread scheduling, so it's not the either-or situation that you describe. The interesting thing about async (especially async-await) is that it is a fluent way to write a program that waits for events (e.g. I/O), without resorting to inefficient designs like thread-per-client in order to achieve concurrency. Software (even in C) has made use of this concept for a long time through poll() and friends; async-await is just a higher level abstraction over the same concept.
- 411111111111111 4y ago> The important difference is in purpose, not implementation. I couldn't disagree stronger on that one. Abstraction is generally good, but this becomes a purely philosophical discussion as soon as you ignore the actual implementation when talking about the differences of how something actuals behaves and should be used. And philosophical discussions like that have their place but are imo pointless in the context of what should be used, when. They're generally better placed in a context of deciding wherever you want to implement an abstraction I do fundamentally agree with the rest of your comment however. that's what I meant with trusting your language to prioritize workloads vs your OS.
- paskozdilar 4y ago> After all threads and async serve different purposes: Threads when you're computing a lot, async when you're waiting a lot, no? It doesn't have to be one or the other. Go uses coroutines and multiplexes them onto multiple OS-level threads (usually as many as there are cores).
- mcronce 4y agoThat's what tokio does under the hood.
- dochtman 4y agoWhich QUIC library did you use? Quinn (the one I work on) comes with an async/await interface.
- ThePhysicist 4y agoQuiche.
- drogus 4y agoJust use `Arc`, it's fine, it will be plenty fast.
- ThePhysicist 4y agoThat's what I ended up doing, Arc<Mutex<...>> for sharing stuff between threads, works really well.
- drogus 4y agoI have recently written a blog post about `Arc` and `Mutex` if you want to get an in-depth view on using them: https://itsallaboutthebit.com/arc-mutex/ https://itsallaboutthebit.com/arc-mutex/
- hawski 4y agoI always wonder about code bases using bolt-on reference counting. I would count C++ shared_ptr and Rust's Arc among them. At this point wouldn't it be better to just use a regular GC language? What I don't like in modern C++ is that almost everything seems to be heap allocated, when it certainly doesn't need to be. In case of GC language you at least get fast allocations and heap compaction. But it is true that resource release is more predictable with RC. On the other hand you get those cascades of releases in RC. As always it is a game of trade offs, but it is something that bothers me.
- drogus 4y ago> At this point wouldn't it be better to just use a regular GC language? No, because you wouldn't slap `Arc` on every single variable. The example from the blog post is a dispatcher and `Arc` is then needed for dispatched functions. The same might be true for queues and other stuff where you need to move stuff between threads, but other than that it would be mostly `Arc` free. As an example: when you write a web service in one of the frameworks like Actix, you typically use `Arc` for stuff that you need to share between all of the handlers like DB connections, queues, counters etc. All of the other stuff would be relatively straightforward Rust without much consideration for lifetimes.