-1

I'd like to keep track of a mutable number across multiple threads. I want to pass this incrementable number (u32) to multiple tokio tasks, each of which can increment it and inspect the value in a safe way.

What is the proper data structure for doing so? I suspect it will involve awaiting to update the value.

Test
  • 962
  • 9
  • 26
  • 1
    Have you read the Rust Book chapter on [Fearless Concurrency](https://doc.rust-lang.org/book/ch16-00-concurrency.html)? In particular, see the section on [Shared-State Concurrency](https://doc.rust-lang.org/book/ch16-03-shared-state.html). – John Kugelman Jun 24 '22 at 00:46
  • Both of these solutions involve possible panics. This is between tokio threads, so I'd like to await updating the value. – Test Jun 24 '22 at 00:53
  • The solutions you link both involve calls to `.lock().unwrap()`, which can panic. – Test Jun 24 '22 at 00:56
  • 1
    Yes but that panics only if the thread underneath panics while it is holding the lock as described in [Poisoning](https://doc.rust-lang.org/std/sync/struct.Mutex.html#poisoning) and also in the [RwLock Poisoning](https://doc.rust-lang.org/std/sync/struct.RwLock.html#poisoning) section – CinchBlue Jun 24 '22 at 00:57
  • @Test if you want to get this question answered such that it requires no panics, please edit the question to say so. otherwise it is not that distinguishable from the suggested duplicate question – CinchBlue Jun 24 '22 at 01:00
  • Awaiting shared variables to see when they change isn't really a thing. Perhaps you want to use [channels](https://tokio.rs/tokio/tutorial/channels) instead. Channels allow threads to communicate by sending and receiving values, and both operations are awaitable. – John Kugelman Jun 24 '22 at 01:00
  • 2
    Another option is to use an atomic value inside of an `Arc`. No need to lock with those. – squiguy Jun 24 '22 at 01:04

1 Answers1

2

Fundamentally, based on the comments thread, this question is close to How can I mutably share an i32 between threads? except with the additional constraint of requiring no panic being possible.

You have 2 major options:

  1. Use architecture-specific atomics with an Arc for sharing across threads like std::sync::atomic::AtomicU32. These are implemented at the hardware level, though you probably still want to consider stuff like unsigned overflow problems as usual. So your type will be Arc<AtomicU32> or so.
  2. Use Arc<Mutex<u64>> which is essentially a more expensive form of (1). Arc is for sharing consistent mutative ownership across threads while Mutex<T> is for making sure "edit-collision" (race condition) doesn't occur for whatever the type T is. If you want to allow for multiple readers at once, use RwLock.

Note that (2) is vulnerable to panics anyways because the thread that holds the write lock can still panic, leaving the lock in a bad state. There are 2 major implementations for mutex locks in Rust, split between std and parking_lot/tokio:

  • std::sync::Mutex and std::sync::RwLock surfaces this error case every time you try to .lock() as PoisonError<T> (docs).
  • tokio and its underlying parking_lot Mutex implementations decided to not implement poison error detection, so just be aware of this.
CinchBlue
  • 6,046
  • 1
  • 27
  • 58
  • For number two, you actually don't even need the `Arc` around a mutex since the `i32` is able to be put in a mutex directly without reference counting. See https://doc.rust-lang.org/std/sync/struct.Mutex.html – RazeLighter777 Jun 24 '22 at 01:41
  • i don't see that in the link, but i do agree with you if you're using scoped threads – CinchBlue Jun 24 '22 at 02:40
  • (1) contains "std::sync". Does that mean it can't be used in async threads? Can you give an example of its use for safe incrementing? It sounds like (1) is the only one that isn't vulnerable to panics. – Test Jun 24 '22 at 03:30
  • I think you're over-focused on avoiding panics. If you don't want to panic you can get rid of the `unwrap()` and handle the failure some other way. But as CinchBlue [said](https://stackoverflow.com/questions/72737869/rust-value-updatable-across-tokio-threads#comment128478766_72737869), you're only going to get a panic from `unwrap()` if some other thread has *already* panicked. The whole "poisoning" mechanism won't panic on its own; it merely propagates panics that happened elsewhere. – John Kugelman Jun 24 '22 at 04:08
  • The "sync" in `std::sync` stands for "synchronization", not "synchronous". So you can use its contents in async code, as shown in [the Tokio tutorial](https://tokio.rs/tokio/tutorial/shared-state). – Jmb Jun 24 '22 at 06:50