Channels

Now that we have learned some about concurrency with Tokio, let’s apply this on the client side. Say we want to run two concurrent Redis commands. We can spawn one task per command. Then the two commands would happen concurrently.

At first, we might try something like:

  1. use mini_redis::client;
  2. #[tokio::main]
  3. async fn main() {
  4. // Establish a connection to the server
  5. let mut client = client::connect("127.0.0.1:6379").await.unwrap();
  6. // Spawn two tasks, one gets a key, the other sets a key
  7. let t1 = tokio::spawn(async {
  8. let res = client.get("hello").await;
  9. });
  10. let t2 = tokio::spawn(async {
  11. client.set("foo", "bar".into()).await;
  12. });
  13. t1.await.unwrap();
  14. t2.await.unwrap();
  15. }

This does not compile because both tasks need to access the client somehow. As Client does not implement Copy, it will not compile without some code to facilitate this sharing. Additionally, Client::set takes &mut self, which means that exclusive access is required to call it. We could open a connection per task, but that is not ideal. We cannot use std::sync::Mutex as .await would need to be called with the lock held. We could use tokio::sync::Mutex, but that would only allow a single in-flight request. If the client implements pipelining, an async mutex results in underutilizing the connection.