В этой статье блога я буду изучать библиотеку
tokio
и приводить примеры асинхронного программирования, которые покажут вам полезность асинхронного программирования.
Допустим, мы - ресторан, и нам нужно приготовить какой-нибудь завтрак. Но для того, чтобы сделать это, мы должны предоставить меню. Наше меню будет состоять из яиц, бекона, картофельных оладий, кофе, тостов, фасоли и вафель. Давайте предположим, что на данный момент в этом ресторане работает только 1 работник и одновременно он обслуживает только 1 клиента.
Во-первых, нам нужно создать функции, которые создают эти варианты завтрака.
async fn eggs() -> String {
println!("Давайте начнем готовить яйца!");
sleep(Duration::from_millis(1000)).await;
println!("Яйца приготовлены!");
"Яйца!".to_string()
}
async fn hasbrowns() -> String {
println!("Давайте начнем готовить несколько тостов!");
sleep(Duration::from_millis(2000)).await;
println!("Тосты приготовлены!");
"Тосты!".to_string()
}
Это два примера. Теперь давайте создадим нашего исполнителя с помощью tokio
и запустим эти функции!
fn run_restaurant() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let mut egg = " ".to_string();
let mut hasbrown = " ".to_string();
use std::time::Instant;
let now = Instant::now();
rt.block_on(
async {
let eggtask = eggs();
let hashbrowntasks = hashbrowns();
egg = eggtask.await;
hasbrown = hashbrowntasks.await;
//
}
);
let elapsed = now.elapsed();
println!("Прошло времени: {:.2?}", elapsed);
}
Ожидаемое затраченное время выполнения должно составлять 2000 миллисекунд, однако в результате 3000 миллисекунд! Вывод в терминале демонстрирует:
Давайте начнем готовить яйца!
Яйца приготовлены!
Давайте начнем готовить несколько тостов!
Тосты приготовлены!
Прошло времени: 3.00s
Эти задачи были выполнены не так, как мы ожидали, верно? Что мы хотели, так это то, что если мы запустим eggs(), то вместо ожидания мы перейдем к следующей задаче hashbrowns()
, которая выполняется сразу же. Тут произойдет следующее: eggs()
и hashbrowns()
будут готовится поочереди! Однако, если мы используем futures::join!
то это будет выполнено как мы задумали.
Давайте начнем готовить яйца!
Яйца приготовлены!
Давайте начнем готовить несколько тостов!
Тосты приготовлены!
Прошло времени: 2.00s
Таким образом, наш код в итоге будет выглядеть следующим образом в потоках:
fn run_restaurant() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let mut egg = " ".to_string();
let mut hasbrown = " ".to_string();
use std::time::Instant;
let now = Instant::now();
rt.block_on(
async {
let eggtask = eggs();
let hashbrowntasks = hashbrowns();
(egg, hasbrown) = futures::join!(eggtask, hashbrowntasks);
//egg = eggtask.await;
//hasbrown = hashbrowntasks.await;
//
}
);
let elapsed = now.elapsed();
println!("Прошло времени: {:.2?}", elapsed);
}
futures::join!
, по сути, “Опрашивает несколько фьючерсов одновременно, возвращая кортеж всех результатов после завершения”, эти задачи должны выполняться одновременно. Мы также можем выполнять эти задачи асинхронно, например, использовать tokio::spawn
.
fn run_restaurant() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let mut egg = " ".to_string();
let mut hasbrown = " ".to_string();
use std::time::Instant;
let now = Instant::now();
rt.block_on(
async {
let eggtask = eggs();
let hashbrowntasks = hashbrowns();
// Давайте начнем готовить яица
let egghandle = tokio::spawn(eggtask);
// Давайте начнем готовить тосты
let hashbrownhandle = tokio::spawn(hashbrowntasks);
hasbrown = hashbrownhandle.await.unwrap();
egg = egghandle.await.unwrap();
//egg = eggtask.await;
//hasbrown = hashbrowntasks.await;
//
}
);
let elapsed = now.elapsed();
println!("Прошло времени: {:.2?}", elapsed);
}
Получаем результат:
Давайте начнем готовить яйца!
Яйца приготовлены!
Давайте начнем готовить несколько тостов!
Тосты приготовлены!
Прошло времени: 2.00s
Обратите внимание, как это, по сути, работает за один и тот же промежуток времени одновременно и параллельно. Это сравнение инкапсулирует полезность асинхронного программирования, мы получаем точно такой же результат за один и тот же промежуток времени между 2 потоками, работающими параллельно для их соответствующих задач, и 1 потоком, работающим одновременно между 2 задачами, которые по существу ограничены вводом-выводом.