Initialize the HashMap
The HashMap
will be shared across many tasks and potentially many threads. To support this, it is wrapped in Arc<Mutex<_>>
.
First, for convenience, add the following type alias after the use
statements.
use bytes::Bytes;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
type Db = Arc<Mutex<HashMap<String, Bytes>>>;
Then, update the main
function to initialize the HashMap
and pass an Arc
handle to the process
function. Using Arc
allows the HashMap
to be referenced concurrently from many tasks, potentially running on many threads. Throughout Tokio, the term handle is used to reference a value that provides access to some shared state.
use tokio::net::TcpListener;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
println!("Listening");
let db = Arc::new(Mutex::new(HashMap::new()));
loop {
let (socket, _) = listener.accept().await.unwrap();
// Clone the handle to the hash map.
let db = db.clone();
println!("Accepted");
tokio::spawn(async move {
process(socket, db).await;
});
}
}
On using std::sync::Mutex
Note, std::sync::Mutex
and not tokio::sync::Mutex
is used to guard the HashMap
. A common error is to unconditionally use tokio::sync::Mutex
from within async code. An async mutex is a mutex that is locked across calls to .await
.
A synchronous mutex will block the current thread when waiting to acquire the lock. This, in turn, will block other tasks from processing. However, switching to tokio::sync::Mutex
usually does not help as the asynchronous mutex uses a synchronous mutex internally.
As a rule of thumb, using a synchronous mutex from within asynchronous code is fine as long as contention remains low and the lock is not held across calls to .await
. Additionally, consider using parking_lot::Mutex
as a faster alternative to std::sync::Mutex
.