223 lines
12 KiB
Markdown
223 lines
12 KiB
Markdown
==Умеет порождать конкурентные задачи через spawn/join/select.==
|
||
|
||
ОР — Знает разницу между кооперативной и вытесняющей многозадачностью
|
||
Допустим, перед вами стоит несколько задач, которые надо выполнить параллельно, но фокусироваться в один момент вы можете только одной задаче. По какой стратегии вы будете переключаться между этими задачами?
|
||
|
||
## Кооперативная и вытесняющая многозадачность
|
||
|
||
Одним из вариантов, который может прийти в голову, это сделать небольшой прогресс в одной задаче, потом переключиться на вторую, сделать в ней небольшой прогресс, и так далее. Такая многозадачность, когда исполнитель решает момент, когда нужно переключаться между задачами, называется **вытесняющей** (так как одна задача как бы вытесняет другую). По такому принципу распределяют задачи на процессор все современные десктопные ОС. А теперь представьте, что момент переключения на другую задачу решаете не вы, а сама задача. Такая многозадачность называется **кооперативной** (так как задачам приходится кооперировать друг с другом за процессорное время).
|
||
|
||
Квиз-Одиночный выбор
|
||
|
||
В чем отличие кооперативной многозадачности от вытесняющей?
|
||
|
||
Исполнитель решает, когда переключаться между задачами
|
||
|
||
Нет, в кооперативной многозадачности момент переключения решают задачи, а не исполнитель
|
||
|
||
Задачи переключаются по истечению времени
|
||
|
||
Нет, задачи могут выполняться сколько угодно
|
||
|
||
Задачи решают, когда можно переключиться с них
|
||
|
||
Правильно, только задачи решают, когда переключаться и могут выполняться сколько угодно
|
||
|
||
Все задачи выполняются только в один поток
|
||
|
||
Нет, такого требования к кооперативности нет
|
||
|
||
## Многозадачность в асинхронном rust
|
||
|
||
ОР — Знает преимущества кооперативной многозадачности
|
||
В rust для асинхронного кода выбрана кооперативная многозадачность. Такой выбор обоснован тем, что переключение между задачами в кооперативной многозадачности существенно быстрее, и дешевле по производительности обходится безопасность по памяти. Если бы многозадачность была вытесняющей, то при переключении задач пришлось бы выполнять дополнительные действия для сохранения состояния задачи, в то время как в кооперативной задачи сами отвечают за свое состояние. При этом у кооперативной многозадачности есть и недостатки: если задача зависает, то другие задачи не могут выполняться. Поэтому во всех современных десктопных ОС выбрана именно вытесняющая многозадачность, ведь иначе какой-нибудь процесс случайно или злонамеренно мог бы повесить всю систему. И поэтому при написании асинхронных функций нужно следить за тем, как долго они выполняются, и если возникает хоть немного занимающая время задача, ее нужно выносить в отдельный поток (в рантаймах есть встроенные инструменты для этого)
|
||
|
||
Квиз-Множественный выбор
|
||
|
||
Выберите преимущества кооперативной многозадачности перед вытесняющей.
|
||
|
||
Задачи выполняются в порядке создания
|
||
|
||
Нет, задачи могут выполняться в любом порядке
|
||
|
||
Быстрее переключение между задачами
|
||
|
||
Да, реализация переключения при кооперативной многозадачности проще и быстрее, чем при вытесняющей
|
||
|
||
Идеальная предсказуемость времени выполнения задачи
|
||
|
||
Нет, кооперативная многозадачность не гарантирует идеальную предсказуемость выполнения. Более того, если одна из других задач будет долго исполняться, выполнение задачи тоже затянется
|
||
|
||
Из-за одной неправильной задачи случайно не остановятся другие
|
||
|
||
Нет, это преимущество вытесняющей многозадачности
|
||
|
||
Быстрее безопасное обращение к памяти
|
||
|
||
Да, благодаря предсказуемости момента переключения, задача успеет закончить свое обращение к памяти
|
||
|
||
## Синтаксис async/await
|
||
|
||
ОР — Умеет использовать async/await синтаксис
|
||
|
||
Для кооперативной многозадачности в самих задачах нужна логика для передачи управления. Для этого в rust существует синтаксис async/await, он выглядит так:
|
||
|
||
```
|
||
// создание асинхронной функции
|
||
async fn task1() {
|
||
do_something().await; // вызов асинхроной функции и ожидание результата от нее
|
||
}
|
||
```
|
||
|
||
Сам по себе вызов такой функции (к примеру, просто `do_something()`) ничего не сделает. Для вызова асинхронной функции нужно использовать .await, если вызывать из асинхронной функции, либо вызывать из рантайма. Для асинхронных замыканий тоже добавляется ключевое слово async:
|
||
|
||
```
|
||
|
||
let closure = async || {
|
||
do_something().await;
|
||
}
|
||
```
|
||
|
||
Или, если нет аргументов, можно опустить `||`:
|
||
|
||
```
|
||
|
||
let closure = async {
|
||
do_something().await;
|
||
}
|
||
```
|
||
|
||
Квиз-Одиночный выбор
|
||
|
||
Что выведет данный код:
|
||
|
||
```
|
||
|
||
fn main() {
|
||
async {
|
||
println!("42");
|
||
}
|
||
}
|
||
```
|
||
|
||
Не скомпилируется из-за синтаксической ошибки
|
||
|
||
Нет, данный код полностью валидный и скомпилируется
|
||
|
||
Не скомпилируется, так как асинхронный код нельзя вызывать из синхронной функции
|
||
|
||
Нет, в данном случае просто создается асинхронное замыкание и ничего не вызывается через .await, поэтому код ничего не выведет
|
||
|
||
Ничего
|
||
|
||
Правильно, данный код просто создаст асинхронное замыкание, без его вызова
|
||
|
||
Выведет в консоль 42
|
||
|
||
Нет, данный лишь создаст асинхронное замыкание, но не вызовет его, поэтому на экран не будет ничего выведено
|
||
|
||
ОР — Умеет настраивать и запускать tokio runtime через макрос
|
||
|
||
ОР — Умеет настраивать и запускать tokio runtime вручную
|
||
|
||
В rust нет встроенного рантайма, они все реализованы в виде библиотек. Самыми популярными являются tokio и smol (раньше еще был async-std, но он теперь discontinued). В данном уроке остановимся на tokio, так как он самый популярный. Чтобы запустить асинхронный код с помошью tokio, можно использовать макрос tokio::main
|
||
|
||
```
|
||
#[tokio::main]
|
||
async fn main() {
|
||
do_something().await;
|
||
}
|
||
```
|
||
|
||
По стандартным настройкам этом макрос cоздает многопоточный рантайм, поэтому он эквивалентен такому коду:
|
||
|
||
```
|
||
|
||
fn main() {
|
||
tokio::runtime::Builder::new_multi_thread()
|
||
.enable_all()
|
||
.build()
|
||
.unwrap()
|
||
.block_on(async {
|
||
do_something().await;
|
||
})
|
||
}
|
||
```
|
||
|
||
Данный код создает многопоточный рантайм tokio, запускает и ожидает завершения функции, переданной в Builder::block_on `.enable_all()` отвечает за возможность использования всего IO tokio и tokio::time Многопоточность в макросе указать явно можно так:
|
||
|
||
```
|
||
#[tokio::main(flavor = "multi_thread")]
|
||
async fn main() {
|
||
do_something().await;
|
||
}
|
||
```
|
||
|
||
Так же в макросе можно указать количество потоков, на которых будут запускаться асинхронные функции (по умолчанию их столько же, сколько потоков в процессоре в системе):
|
||
|
||
```
|
||
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
|
||
async fn main() {
|
||
//...
|
||
}
|
||
```
|
||
|
||
То же самое, но без макроса:
|
||
|
||
```
|
||
|
||
fn main() {
|
||
tokio::runtime::Builder::new_multi_thread()
|
||
.worker_threads(8)
|
||
.enable_all()
|
||
.build()
|
||
.unwrap()
|
||
.block_on(async {
|
||
//...
|
||
})
|
||
}
|
||
```
|
||
|
||
Чтобы рантайм был однопоточным, можно в макросе указать параметр flavor:
|
||
|
||
```
|
||
#[tokio::main(flavor = "current_thread")]
|
||
async fn main() {
|
||
//...
|
||
}
|
||
```
|
||
|
||
То же самое, но без макроса:
|
||
|
||
```
|
||
|
||
fn main() {
|
||
tokio::runtime::Builder::new_current_thread()
|
||
.enable_all()
|
||
.build()
|
||
.unwrap()
|
||
.block_on(async {
|
||
//...
|
||
})
|
||
}
|
||
```
|
||
|
||
Квиз-Одиночный выбор
|
||
|
||
На скольких потоках будут запускаться асинхронные функции, если просто использовать макрос `#[tokio::main]`:
|
||
|
||
1
|
||
|
||
Нет, по умолчанию tokio будет запускать задачи на стольких потоках, сколько потоков у процессора в системе
|
||
|
||
Ошибка, нужно указать явно
|
||
|
||
Нет, указывать явно не нужно, по умолчанию tokio будет запускать задачи на стольких потоках, сколько потоков у процессора в системе
|
||
|
||
8
|
||
|
||
Нет, по умолчанию tokio будет запускать задачи на стольких потоках, сколько потоков у процессора в системе
|
||
|
||
Столько, сколько потоков в процессоре на данной системе
|
||
|
||
Правильно, без явного указания потоков будет столько же, сколько их в процессоре в системе |