Для чего нужна асинхронность?
Все мы любим то, что Rust позволяет нам писать быстрые и безопасные приложения. Но для чего писать асинхронный код?
Асинхронный код позволяет нам запускать несколько задач параллельно в одном потоке ОС. Если вы ходите одновременно загрузить две разных web-страницы в обычном приложении, вы должны разделить работу между двумя разным потоками, как тут:
# #![allow(unused_variables)] #fn main() { fn get_two_sites() { // Spawn two threads to do work. let thread_one = thread::spawn(|| download("https:://www.foo.com")); let thread_two = thread::spawn(|| download("https:://www.bar.com")); // Wait for both threads to complete. thread_one.join().expect("thread one panicked"); thread_two.join().expect("thread two panicked"); } #}
Для многих приложений это замечательно работает - в конце концов,
потоки были разработаны именно для этого: запускать несколько
разных задач одновременно. Однако, они имеют некоторые
ограничения. В процессе переключения между разными потоками и
обменом данными между ними возникает много накладных расходов.
Даже поток, который сидит и ничего не делает, использует ценные
системные ресурсы. Асинхронный код предназначен для устранения
этих проблем. Мы можем переписать функции выше используя Rust
нотацию async
/.await
, которая позволяет
нам запустить несколько задач одновременно, не создавая несколько потоков:
# #![allow(unused_variables)] #fn main() { async fn get_two_sites_async() { // Create a two different "futures" which, when run to completion, // will asynchronously download the webpages. let future_one = download_async("https:://www.foo.com"); let future_two = download_async("https:://www.bar.com"); // Run both futures to completion at the same time. join!(future_one, future_two); } #}
В целом, асинхронные приложения могут быть намного быстрее и
использовать меньше ресурсов, чем соответствующая
многопоточная реализация. Однако, есть и обратная сторона.
Потоки изначально поддерживаются операционной системой и их
использование не требует какой-либо специальной модели
программирования - любая функция может создать поток и вызов
функции, использующей поток, обычно так же прост, как вызов
обычной функции. Тем не менее, асинхронные функции требует
специальной поддержки со стороны языка или библиотек. В Rust,
async fn
создаёт асинхронную функцию, которая
возвращает Future
. Для выполнения тела функции,
возвращённая Future
должна быть завершена.
Важно помнить, что традиционные приложения с потоками могут
быть вполне эффективными и предсказуемость Rust и небольшой
объём памяти могут значить, что вы можете далеко продвинуться
без использования async
. Повышенная сложность
асинхронной модели программирования не всегда стоит этого и
важно понимать, когда ваше приложения будет лучше работать с
использованием просто поточной модели.