Recommended resources to learn MPSC in Rust

Hi, I am looking to multithread my ray tracer, and it seems an MPSC system with multiple producer threads and one consumer thread would be the best way to go. However, I don't have much practical experience writing multithreaded programs (pragma omp does not count), so I was wondering if there are some nice resources that can help me out.

I'm already going through Chapter 16 of TRPL for the fundamentals of multithreading in rust, and the docs for MPSC. Apart from that, does anyone know of any good resources?

Thanks!

(Note that I'm not a big expert on Concurrency in Rust, and I haven't fully examined everything below, but it is certainly a topic of great interest to me)

In no particular order, here are some of the things I can remember to look up:

I could have sworn that there were another one or two good resources out there, but I can't seem to find them now. When it comes to searching for other resources, my recommendations would be to start with the most recent articles, since there's been a lot of change with regards to concurrency for Rust in the last few years, and also to prioritise material written by the Rust team and the authors of the various concurrency crates out there. This is not to say that others wouldn't have written a lot of good stuff, but it is possible that some material out there was written by people who don't really know what they're talking about, and it could be difficult to spot.

3 Likes

There isn't that much magic to them. Write the results to a Vec and send it to the parent thread.

Things you may want to be aware of:

  • The crossbeam crate has a more efficient channel than the one in std.
  • An alternate way of sending values is to return it from the closure given to thread::spawn. In this case the value can be obtaining by calling join on the JoinHandle returned by the spawn function.
  • The rayon crate also has some good methods for implementing this kind of stuff. For example you could write a method that writes the result to a &mut output slice, and it could use the rayon::join method to split that task into two tasks (by calling split_mut on the slice, you can split it into two &mut references). Using this method, each half of the task could again split itself, until you have very small tasks. This would automatically run it on a thread pool.
4 Likes

@Jarak
Thanks for the recommendations!

Yeah, it's kind of important to filter out the results that look to be too old, and also possibly not as reliable as first impressions might look to deliver.

@alice

Thanks for the tips! It indeed seems to be writing results and sending them to a parent thread. However, is there any data that indicates crossbeam is more efficient than std? Just curious...

And I guess with std::sync::mspc or crossbeam's mspc, I'd also need a separate threadpool.

You can read this blogpost that proposes to replace the one in std with the one from crossbeam. The main arguments against it is that the api is not fully backwards compatible, so replacing them is not easy.

3 Likes

Wow, I was not aware of such a history of crossbeam. It was a very interesting read, thank you!

Also, I'm guessing we need a threadpool of sorts to work with the scoped threads. Is there any lightweight crate that does this nicely? Or is it better to just write it one from scratch?

The crossbeam crate provides scoped threads, but not thread pools. The typical crate that provides scoped thread pools is rayon.

If you do not need it to be scoped, you can use threadpool. This requires sending an Arc instead of a reference, as without the scoping, you cannot send a reference.

Awesome! While we're at it, I also found the crate called scoped_threadpool. Do you have an opinion on using this vs. the non-scoped threadpool?

Also, out of curiousity, apart from the implementation detail, are there any other things to keep in mind while deciding between scoped or non-scoped threadpools, such as runtime cost or something like that?

Thanks a lot for the patience in answering all the questions by the way, really appreciate it. :slight_smile:

Ah, I wasn't aware of the scoped_threadpool crate. When comparing this to the threadpool crate, I would recommend choosing the scoped one if you need it to be scoped, and the other one otherwise.

Typically when I write threaded code, I would choose one of these methods:

  1. If my code is CPU-bound, I would default to rayon.
  2. If my code is IO-bound, I would use futures with tokio.

In this case your code is CPU-bound, so I would personally use rayon.

The main difference between a scoped thread pool and a non-scoped version is that the scoped thread pool allows sending stuff with non-'static references, which can make some stuff much easier, while the non-scoped thread pool can be easier to use, because you do not have to block while waiting for the thread pool to run.

The runtime costs are basically equivalent if you pass data by-value or by-Arc. If you have to clone the data to use a non-scoped thread pool, then be aware of that clone.

4 Likes

All right. Thanks so much for the advice!