Async/.await
14 декември 2021
Паралелизъм и конкурентност
Кога искаме паралелизъм?
- малко на брой задачи
- тежки от към процесорно време
Кога искаме конкурентност?
- потенциално много на брой задачи
- леки от към процесорно време
- дълъг период на изчакване
Кога искаме конкурентност?
- потенциално много на брой задачи
- леки от към процесорно време
- дълъг период на изчакване
- основно I/O операции
- напр. http заявки, заявки до бази данни
Модели за конкурентност
Kernel space
- нишки на операционната система
Модели за конкурентност
Kernel space
- нишки на операционната система
- паралелизъм и конкурентност
Модели за конкурентност
Kernel space
- нишки на операционната система
- паралелизъм и конкурентност
- предоставени са ни наготово от операционната система
Модели за конкурентност
Kernel space
- нишки на операционната система
- паралелизъм и конкурентност
- предоставени са ни наготово от операционната система
- но не скалират добре (за стотици или хиляди задачи)
Модели за конкурентност
User space
- event-driven с callback функции
Модели за конкурентност
User space
- event-driven с callback функции
- корутини, зелени нишки
Модели за конкурентност
User space
- event-driven с callback функции
- корутини, зелени нишки
- машини на състоянията
Модели за конкурентност
User space
- event-driven с callback функции
- корутини, зелени нишки
- машини на състоянията
- актьори
Асинхронно програмиране
- модел за програмиране
- позволява изпълнение на много задачи конкурентно
- запазва вида на кода - кода изглежда като обикновенен синхронен код
1
2
3
4
5
6
7
8
9
async fn get_two_sites_async() {
// Create 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
- от rust 1.39 (ноември 2019)
Асинхронно програмиране в Rust
- от rust 1.39 (ноември 2019)
- Ръст предоставя async/await синтаксиса
Асинхронно програмиране в Rust
- от rust 1.39 (ноември 2019)
- Ръст предоставя async/await синтаксиса
- Ръст предоставя фундаменталните типове и trait-ове
Асинхронно програмиране в Rust
- от rust 1.39 (ноември 2019)
- Ръст предоставя async/await синтаксиса
- Ръст предоставя фундаменталните типове и trait-ове
- библиотеката
futuresпредоставя множество полезни функции и макроси
Асинхронно програмиране в Rust
- от rust 1.39 (ноември 2019)
- Ръст предоставя async/await синтаксиса
- Ръст предоставя фундаменталните типове и trait-ове
- библиотеката
futuresпредоставя множество полезни функции и макроси - за изпълнение на асинхронен код е необходим async runtime, който се предоставя от библиотеки като
tokioилиasync_std
Асинхронно програмиране в Rust
Библиотечни функции:
- използват се типовете от
stdиfutures - обикновенно работят с всеки async runtime
Изпълними програми:
- трябва да се избере точно един async runtime
Async/.await в Rust
async функции
- в превод
async fn(...) -> T⇒fn(...) -> impl Future<Output = T> five()е от типimpl Future<Output = u8>five().awaitе от типu8
1
2
3
4
// връща анонимен тип, който имплементира trait-а `Future<Output = u8>`
async fn five() -> u8 {
5
}
#![allow(dead_code)] // връща анонимен тип, който имплементира trait-а `Future
Async/.await в Rust
async блокове
1
2
3
4
5
6
7
8
9
use std::future::Future;
fn ten() -> impl Future<Output = u8> {
// връща анонимен тип, който имплементира trait-а `Future<Output = u8>`
async {
let x: u8 = five().await;
x + 5
}
}
#![allow(dead_code)] use std::future::Future; fn ten() -> impl Future
Async/.await в Rust
.await
.awaitе постфиксен оператор.awaitможе да се използва само вasync fnилиasync {}
1
2
3
4
5
6
7
8
9
10
11
async fn five() -> u8 {
5
}
async fn ten() -> u8 {
five().await + 5
}
fn main() {
let x = ten().await;
}
error[E0728]: `await` is only allowed inside `async` functions and blocks --> src/bin/main_c247aff303b227bd4f1fa2405d3f7ad357520e11.rs:10:13 | 9 | fn main() { | ---- this is not `async` 10 | let x = ten().await; | ^^^^^^^^^^^ only allowed inside `async` functions and blocks For more information about this error, try `rustc --explain E0728`. error: could not compile `rust` due to previous error
async fn five() -> u8 {
5
}
async fn ten() -> u8 {
five().await + 5
}
fn main() {
let x = ten().await;
}
Async/.await в Rust
.await
.awaitе постфиксен оператор.awaitможе да се използва само вasync fnилиasync {}
1
2
3
4
5
6
7
8
9
10
11
12
13
async fn five() -> u8 {
5
}
async fn ten() -> u8 {
five().await + 5
}
fn main() {
let x = ::futures::executor::block_on(async {
ten().await
});
}
async fn five() -> u8 {
5
}
async fn ten() -> u8 {
five().await + 5
}
fn main() {
let x = ::futures::executor::block_on(async {
ten().await
});
}
Trait Future
1
2
3
4
5
6
7
8
9
10
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
}
pub enum Poll<T> {
Ready(T),
Pending,
}
use std::task::Context;
use std::pin::Pin;
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll;
}
pub enum Poll {
Ready(T),
Pending,
}
fn main() {}
- тип, който имплементира
Future, съдържа всичката информация нужна за изпълнението на асинхронна операция - игнорирайте
PinиContextзасега
Trait Future
- future-а е само структура
- по само себе си не прави нищо (той е мързелив)
- за да започне работа трябва някой да му извика
poll
1
2
3
4
5
async fn foo() {
println!("foo");
}
let foo_future = foo();
#![allow(unused_variables)]
fn main() {
async fn foo() {
println!("foo");
}
let foo_future = foo();
}
Изпълнение на future
Future може да се изпълни
- като му се извика
.awaitвasyncблок или функция - като се подаде на executor
Изпълнение на future
Futures извършват прогрес, когато някой executor им извика poll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let fut1 = async_func1();
::futures::executor::block_on(fut1);
}
async fn async_func1() -> i32 {
let fut2 = another_async_func();
let x = fut2.await;
let y = regular_func();
let fut3 = make_call_to_db();
let z = fut3.await;
x + y
}
fn main() {
let fut1 = async_func1();
::futures::executor::block_on(fut1);
}
async fn async_func1() -> i32 {
let fut2 = another_async_func();
let x = fut2.await;
let y = regular_func();
let fut3 = make_call_to_db();
let z = fut3.await;
x + y
}
async fn another_async_func() -> i32 { 0 }
async fn make_call_to_db() -> i32 { 0 }
fn regular_func() -> i32 { 0 }Изпълнение на future
За да могат да се прекъсват, всички нужни локални променливи се запомнят във future-а.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let fut1 = async_func1();
::futures::executor::block_on(fut1);
}
async fn async_func1() -> i32 {
let fut2 = another_async_func();
let x = fut2.await;
let y = regular_func();
let fut3 = make_call_to_db();
let z = fut3.await;
x + y
}
fn main() {
let fut1 = async_func1();
::futures::executor::block_on(fut1);
}
async fn async_func1() -> i32 {
let fut2 = another_async_func();
let x = fut2.await;
let y = regular_func();
let fut3 = make_call_to_db();
let z = fut3.await;
x + y
}
async fn another_async_func() -> i32 { 0 }
async fn make_call_to_db() -> i32 { 0 }
fn regular_func() -> i32 { 0 }
1
2
3
4
5
enum Fut1 {
AtAwait1 { fut2: Fut2 },
AtAwait2 { x: i32, y: i32, fut3: Fut3 },
Done,
}
Futures екосистемата
- преди да бъдат добавени към
stdfutures съществуваха в rust екосистемата като библиотеката futures
Futures екосистемата
- преди да бъдат добавени към
stdfutures съществуваха в rust екосистемата като библиотеката futures - в стандартната библиотека са стабилизирани част от
futures 0.3
Futures екосистемата
- преди да бъдат добавени към
stdfutures съществуваха в rust екосистемата като библиотеката futures - в стандартната библиотека са стабилизирани част от
futures 0.3 - но има още неща, които не са добавени
- композиране на futures
StreamиSinktraitsAsyncReadиAsyncWritetraitsselect!,join!block_on- и други полезни неща
Futures екосистемата
- за да използвате async/await трябва да си изберете executor
Futures екосистемата
- за да използвате async/await трябва да си изберете executor
- https://docs.rs/tokio - framework за създаване на програми, работещи с мрежата
Futures екосистемата
- за да използвате async/await трябва да си изберете executor
- https://docs.rs/tokio - framework за създаване на програми, работещи с мрежата
- https://docs.rs/async-std - огледално копие на стандартната библиотека, но всички блокиращи функции са заменени с асинхронни
Futures екосистемата
- за да използвате async/await трябва да си изберете executor
- https://docs.rs/tokio - framework за създаване на програми, работещи с мрежата
- https://docs.rs/async-std - огледално копие на стандартната библиотека, но всички блокиращи функции са заменени с асинхронни
- https://docs.rs/smol - малък, прост executor
Futures екосистемата
Съвместимост
- Не всички async библиотеки са съвместими една с друга!
Futures екосистемата
Съвместимост
- Не всички async библиотеки са съвместими една с друга!
- Асинхроннен вход/изход, таймери и подобни (и всички библиотеки които надграждат над това) обикновенно могат да работят само на определен executor
Futures екосистемата
Съвместимост
- Не всички async библиотеки са съвместими една с друга!
- Асинхроннен вход/изход, таймери и подобни (и всички библиотеки които надграждат над това) обикновенно могат да работят само на определен executor
- Всякакъв друг асинхронен код, като комбинатори, синхронизационни примитиви и подобни обикновенно могат да работят на произволен executor
Futures екосистемата
Съвместимост
- Не всички async библиотеки са съвместими една с друга!
- Асинхроннен вход/изход, таймери и подобни (и всички библиотеки които надграждат над това) обикновенно могат да работят само на определен executor
- Всякакъв друг асинхронен код, като комбинатори, синхронизационни примитиви и подобни обикновенно могат да работят на произволен executor
- https://rust-lang.github.io/async-book/08_ecosystem/00_chapter.html
Малък демо проект
Web crawler, който събира и напечатва линкове към блогове от https://this-week-in-rust.org
Използвани библиотеки
Подводни камъни
Блокиращи операции
- когато ползваме async е важно да нямаме блокиращи операции
Подводни камъни
Блокиращи операции
- когато ползваме async е важно да нямаме блокиращи операции
- блокиращите операции блокират текущата нишка
Подводни камъни
Блокиращи операции
- когато ползваме async е важно да нямаме блокиращи операции
- блокиращите операции блокират текущата нишка
- async executor-ите ползват ограничено количество нишки за изпълнение на задачи
Подводни камъни
Блокиращи операции
- когато ползваме async е важно да нямаме блокиращи операции
- блокиращите операции блокират текущата нишка
- async executor-ите ползват ограничено количество нишки за изпълнение на задачи
- в резултат можем лесно да си забавим или тотално забием програмата
Подводни камъни
Примитиви за синхронизация
- примитивите в
std::syncблокират текущата нишка
Подводни камъни
Примитиви за синхронизация
- примитивите в
std::syncблокират текущата нишка - вместо това трябва да се използват async версии, които блокират само текущата задача
Подводни камъни
Примитиви за синхронизация
- примитивите в
std::syncблокират текущата нишка - вместо това трябва да се използват async версии, които блокират само текущата задача
async_std::sync
Подводни камъни
Примитиви за синхронизация
- примитивите в
std::syncблокират текущата нишка - вместо това трябва да се използват async версии, които блокират само текущата задача
async_std::synctokio::sync
Подводни камъни
Тежки операции
- други блокиращи операции, като
- тежки изчисления
- блокиращ код, който не може да се промени
Подводни камъни
Тежки операции
- други блокиращи операции, като
- тежки изчисления
- блокиращ код, който не може да се промени
- трябва да се изпълняват на отделна ОС нишка
Подводни камъни
Тежки операции
- други блокиращи операции, като
- тежки изчисления
- блокиращ код, който не може да се промени
- трябва да се изпълняват на отделна ОС нишка
- може да се пуска нова нишка за всяка операция
Подводни камъни
Тежки операции
- други блокиращи операции, като
- тежки изчисления
- блокиращ код, който не може да се промени
- трябва да се изпълняват на отделна ОС нишка
- може да се пуска нова нишка за всяка операция
- или да се използва thread pool
Допълнителни материали
- https://rust-lang.github.io/async-book/ - официалната async/await книга
- https://book.async.rs/ - книгата за async std, имат добър туториал за имплементация на чат сървър
- https://www.youtube.com/watch?v=L7X0vpAU-sU - презентация за async-std