std::sync::Mutex and tokio::sync::Mutex look almost the same. The difference is what happens when the lock is already taken.
std::sync::Mutex::lock blocks the thread. On an async runtime that thread is a worker shared by many tasks, so blocking it stalls all of them. Worse, an async runtime may have far fewer threads than tasks, and enough blocked workers is a deadlock.
tokio::sync::Mutex::lock is async. It yields, the runtime runs something else, and your task wakes when the lock is free.
use tokio::sync::Mutex;
let guard = shared.lock().await;A std::sync::MutexGuard is not Send. Hold one across an .await and the whole future stops being Send, so tokio::spawn refuses it. The compiler catches the mistake, and the error is confusing the first time you hit it.
So: if the critical section contains an .await, use tokio::sync::Mutex. If it does not, keep std::sync::Mutex, which is faster and simpler.
Holding a lock across an await is still a real cost. Every other caller is stopped for the duration. Sometimes that is the entire point.
Build a cache whose misses are filled by an async loader, where a key that several tasks ask for at once is loaded exactly once.
load_value is written for you. It sleeps, then returns a string.
pub struct Cache {
entries: Mutex<HashMap<String, String>>,
loads: AtomicUsize,
}Implement four methods:
new() -> Self, an empty cacheasync get_or_load(&self, key: &str) -> String, the value for key, calling load_value only on a missloads(&self) -> usize, how many times the loader has runasync snapshot(&self) -> Vec<(String, String)>, every cached pair sorted by keyEight tasks calling get_or_load("config") at the same time must leave loads() at 1, and all eight must get the same value.
Vec<(String, String)> sorts by key first, which is what you want.Ordering::SeqCst is a fine default for the counter here.#[derive(Default)] is already on the struct, so new can be Self::default().get_or_load, take let mut entries = self.entries.lock().await; first, before anything else.entries.get(key) is Some.snapshot, collect cloned pairs out of the map, then sort().std::sync::Mutex and tokio::sync::Mutex look almost the same. The difference is what happens when the lock is already taken.
std::sync::Mutex::lock blocks the thread. On an async runtime that thread is a worker shared by many tasks, so blocking it stalls all of them. Worse, an async runtime may have far fewer threads than tasks, and enough blocked workers is a deadlock.
tokio::sync::Mutex::lock is async. It yields, the runtime runs something else, and your task wakes when the lock is free.
use tokio::sync::Mutex;
let guard = shared.lock().await;A std::sync::MutexGuard is not Send. Hold one across an .await and the whole future stops being Send, so tokio::spawn refuses it. The compiler catches the mistake, and the error is confusing the first time you hit it.
So: if the critical section contains an .await, use tokio::sync::Mutex. If it does not, keep std::sync::Mutex, which is faster and simpler.
Holding a lock across an await is still a real cost. Every other caller is stopped for the duration. Sometimes that is the entire point.
Build a cache whose misses are filled by an async loader, where a key that several tasks ask for at once is loaded exactly once.
load_value is written for you. It sleeps, then returns a string.
pub struct Cache {
entries: Mutex<HashMap<String, String>>,
loads: AtomicUsize,
}Implement four methods:
new() -> Self, an empty cacheasync get_or_load(&self, key: &str) -> String, the value for key, calling load_value only on a missloads(&self) -> usize, how many times the loader has runasync snapshot(&self) -> Vec<(String, String)>, every cached pair sorted by keyEight tasks calling get_or_load("config") at the same time must leave loads() at 1, and all eight must get the same value.
Vec<(String, String)> sorts by key first, which is what you want.Ordering::SeqCst is a fine default for the counter here.#[derive(Default)] is already on the struct, so new can be Self::default().get_or_load, take let mut entries = self.entries.lock().await; first, before anything else.entries.get(key) is Some.snapshot, collect cloned pairs out of the map, then sort().