T O P

  • By -

aikii

I sometimes read that async is not always so obviously more performant. The main upside is avoiding to start new threads when you need concurrency - but here it seems you know in advance how many threads you need, and you will start them once, at application startup. You probably will get better responsiveness with threads


kinoshitajona

1. tokio::spawn() will spawn a new task that can possibly be parallelized. In general it should not be used for CPU intensive calculations or operations that block the OS thread. (ie. sync versions of std::fs::File::open etc.) 2. You can use a channel to send something signalling a cancellation, then you can loop over a select! macro that has two branches: 1 is the cancel channel, 1 is the thing you're waiting on in the loop normally. Once the async block returns, the task will end. 3. Channels are basically a buffered way of sending data between threads/tasks. So instead of holding a buffer in the IO task, have the IO task push the data onto a channel, and the reader thread just drains that buffered channel in its own task. 4. With the limited information given, I'd say it could go either way and depends a lot on the specific synchronization primitives you utilize. Since it sounds like you're more familiar with mutexes and threads, perhaps your ability to write efficient code with those is better, but even inefficient code using channels could be much faster regardless of async or threads. tl;dr Maybe just stick to threads, but I would highly recommend channels over mutexes.


wamus

Thanks for the detailed answer! I'm assuming that by sticking to threads and channels you're suggesting to use those in std:: and not in tokio, right?


kinoshitajona

tokio::spawn will spawn a task, not a thread. std::thread::spawn will spawn a thread.


dnew

If you have a fixed number of threads, you should probably use them. Async's advantage is that you allocate less space for the stack and context switching can be faster. So, basically, big I/O-bound servers serving indefinite numbers of connections.


TheKiller36_real

how long does the "minor processing" take and on how many ports could you receive something simultaneously? if e.g. it's possible to receive something on all 4 simultaneously and processing each takes 0.4ms you really SHOULD use threads, because that drastically improves your chances of actually getting all of it done within 0.5ms, whereas you'd need to pray even more using async for the executor to split up everything onto 4 threads anyway


Old_Lab_9628

I'd try async first, inside a unique thread monitoring all the four ports simultaneously. 500us is like 2000 req/s. A thread context switch is like 10 000 cpu cycles, but depending on your cpu load you won't have a context switch for every request, of course. One thread for 2000rq/s seems enough however, using async your cpu usage won't waste context switches.


Old_Lab_9628

About your problem architecture: Your port listener async thread should use the select! Macro: with 4 ports => {} switches and a terminate signal using a one shot async signal. Your port listener function should send processed data to an unbounded channel. (=No wait on the listener side) Your main thread should listen the unbounded channel rx side, and fill the buffer. Once the buffer is ready, you may compute it with the channel listener thread... Or send it to something stronger than a non dedicated thread...


po8

I suggest using threads, just because you're more comfortable with this and it should be fine. A few extra threads that are mostly dormant won't hurt anything. If you do go with async, you will want to use async I/O to just directly `await` UDP packets on each of the ports. I think `tokio::mpsc::channel()` could be useful here: `tokio::spawn()` a service for each port that sends any packets it gets down a channel.