Sync mutex in async program

Hi,

What is better if I need sync mutex in an async program:

  1. just use std::sync::Mutex
  2. do something like the below:
use async_std::sync::{Mutex,Arc};
let shared = Arc::new(Mutex::new(my_val));
{ // sync block
let my_val = loop {
    if let Some(val) = shared.try_lock() {
        break val;
    }
...
}
}

You should use an async Mutex only if you need to hold the lock across an .await. Otherwise you should use a regular Mutex from std or parking_lot. You should certainly not busy-loop like your code does.

3 Likes

...or if waiting for the lock might take much time, since std Mutex is blocking and will force all tasks on the current thread to stop.

3 Likes

Didn't think about that. Using synchronous Mutex inside an async environment could easily create deadlocks(*). So it seems that solution 2 (the busy loop) or something similar is always preferable.

(*)

  1. runtime is executing future A that locks the sync Mutex
  2. runtime stop future A for some reason and start executing future B
  3. future B try to lock the sync Mutex

The busy loop is never preferable, I'm not sure where you got that from.

Note that, within Rust, the runtime will never stop the future unless you use a .await. If you need to use a mutex which you lock, and then do a .await before unlocking, you should use an async Mutex (from async_std or tokio, for instance).

If you don't need to .await while the Mutex is locked, you should prefer a sync Mutex from std or parking_lot, as that will generally give you the best multithreaded performance cooperating with the operating system.

The one uncommon use case cited above is when you have extremely computationally intensive work (i.e. something that takes close to a second to complete) you need to do while the Mutex is locked, at which point an async Mutex might again become preferable, to avoid blocking other Futures on other threads. This use case actually causes some more complicated implications on an async executor, so should be considered in further detail if you actually need it.

1 Like

If I can't use an async Mutex (cause a need it in a impl trait that is not async). I could use the sync Mutex without risking any deadlock cause the lock is acquired and released in the syncrounous block that can never call .await so I'm 100% sure that the runtime will never stop before I release the lock.

Is that right?

Yes, if all of your code is synchronous (does not use async/.await or Future) you can use a synchronous lock without risk of deadlock.

This post from @alice might be of interest too.

Also see the Tokio Tutorial chapter on Shared state. Note that ironically std::sync::Mutex std::sync::MutexGuard isn't Send :laughing:, but tokio::sync::Mutex tokio::sync::MutexGuard is Send.

(Edit: Corrected the mistake)

In some cases, it may make sense to use the mutex from std::sync, in other cases it is important to use an async-aware mutex.

What are you talking about?

Sorry, I made a mistake there. I meant:

Will correct that in my post above.

And the follow up from @steffahn:

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.