I've prepared the example.
I need to run some jobs within the struct's method in tokio::spawn but also I need to have mutable access to the struct. Without mutability it works well by means of Arc, but to have possibility to mutate the struct's object, I need to use Arc<Mutex and it breaks everything.
There's Arc::get_mut. Make sure to read the documentation so that you understand the caveats.
Unfortunately, it doesn’t fit my needs. I have several references to the object.
This version seems to work:
Not sure if it's what you want though API or logic-wise.
Edit: It doesn't seem to receive anything though
In any case not using the self
magic type with types that receive Self
as a sendable form is better. So instead of:
pub fn run(self: Arc<Self>)
I used:
pub fn run(me: Arc<Mutex<Self>>)
And I call this by cloning the Arc
:
Some::run(Arc::clone(&s)).await;
With this though I used a lot of locks and this might be deadlock prone..
I've found a solution but I don't know if it is a good one. It works as I want but the new method returns Arc<Mutex>.
If Some
won't be mutated from several places and only from one place at a given time (ie. clear_counter
) then instead of Arc<Mutex>>
you can use an Arc<RwLock>>
. This will enable simultaneous reads from multiple places happen at the same time potentially improving performance.
Thanks for the reminder; I've been considering replacing Mutex
with RwLock
, but have postponed it.
Do note that RwLock is potentially just as slow as (or slower) Mutex, as multiple core still need to write to the lock count. This will cause cache line bounces which will limit scalability.
If the read lock is held for long periods this will be less important, but for any locks held for short periods this can be a large overhead.
There are other more specialised synchronisation primitives that can be used instead (rcu, hazard pointers, ...) for read heavy workloads. And others that do well with write heavy workloads (seqlock is great for plain old data with a single writer and many readers, etc).
There are also specialised lock free or wait free algorithms for specific common use cases. With various trade offs. (But this is a whole rabbit hole on its own, I once read a book that had an entire chapter on just counting in parallel).
In general, if performance matters (according to your profiling) I would suggest investigating alternatives to RwLock that might be more suitable to your application.
And I would never recommend RwLock for performance without a lot of caveats.
In the specific case where T
is an integer you can use an atomic type instead, for example Arc<AtomicU64>
.
Be aware that this introduces you to the fun new world of memory orderings, the very short version is if you want the access to the value to behave like the aquire or the release side of taking a mutex lock. For the case of your example you can probably get away with just using Relaxed though, as the channel to get the result will work as a barrier too, as far as I understand: you should probably look into loom to verify that though.
Atomics can be great when applicable. A great resource for learning how to use them correctly is the free ebook https://marabos.nl/atomics/