164 lines
11 KiB
Markdown
164 lines
11 KiB
Markdown
Кооперативная
|
||
Вытесняющая - пример: процессор
|
||
преимуще
|
||
|
||
для синтаксиса - объяснить, что вызов функции не исполняет ее сразу, как в js, а возвращает (пока что) какое-то значение, которое исполнит функцию по вызову .await на ней
|
||
|
||
макрос - привести пример #[tokio::main] и #[tokio::main(...)]
|
||
|
||
## Start:
|
||
|
||
Допустим, перед вами стоит несколько задач, которые надо выполнить параллельно, но фокусироваться в один момент вы можете только одной задаче. По какой стратегии вы будете переключаться между этими задачами?
|
||
|
||
## Кооперативная и вытесняющая многозадачность
|
||
Одним из вариантов, который может прийти в голову, это сделать небольшой прогресс в одной задаче, потом переключиться на вторую, сделать в ней небольшой прогресс, и так далее. Такая многозадачность, когда исполнитель решает момент, когда нужно переключаться между задачами, называется **вытесняющей** (так как одна задача как бы вытесняет другую). По такому принципу распределяют задачи на процессор все современные десктопные ОС.
|
||
А теперь представьте, что момент переключения на другую задачу решаете не вы, а сама задача. Такая многозадачность называется **кооперативной** (так как задачам приходится кооперировать друг с другом за процессорное время).
|
||
|
||
Квиз-Одиночный выбор
|
||
В чем отличие кооперативной многозадачности от вытесняющей?
|
||
1. Исполнитель решает, когда переключаться между задачами
|
||
Нет, в кооперативной многозадачности момент переключения решают задачи, а не исполнитель
|
||
2. Задачи переключаются по истечению времени
|
||
Нет, задачи могут выполняться сколько угодно
|
||
3. Задачи решают, когда можно переключиться с них
|
||
Правильно, только задачи решают, когда переключаться и могут выполняться сколько угодно
|
||
4. Все задачи выполняются только в один поток
|
||
Нет, такого требования к кооперативности нет
|
||
|
||
Квиз-Множественный выбор
|
||
У каких из этих задач кооперативная многозадачность?
|
||
1. Потоки процессов на Linux
|
||
2. У модема по adsl для звонков и интернета (пользоваться интернетом, пока идет звонок, нельзя)
|
||
3. Пер
|
||
4. Очередь на печать на принтере
|
||
5. Обработка
|
||
## Многозадачность в асинхронном rust
|
||
В rust для асинхронного кода выбрана кооперативная многозадачность. Такой выбор обоснован тем, что переключение между задачами в кооперативной многозадачности существенно быстрее, и дешевле по производительности обходится безопасность по памяти. Если бы многозадачность была вытесняющей, то при переключении задач пришлось бы выполнять дополнительные действия для сохранения состояния задачи, в то время как в кооперативной задачи сами отвечают за свое состояние. При этом у кооперативной многозадачности есть и недостатки: если задача зависает, то другие задачи не могут выполняться. Поэтому во всех современных десктопных ОС выбрана именно вытесняющая многозадачность, ведь иначе какой-нибудь процесс случайно или злонамеренно мог бы повесить всю систему. И поэтому при написании асинхронных функций нужно следить за тем, как долго они выполняются, и если возникает хоть немного занимающая время задача, ее нужно выносить в отдельный поток (в рантаймах есть встроенные инструменты для этого)
|
||
|
||
Квиз-Множественный выбор
|
||
Выберите преимущества кооперативной многозадачности перед вытесняющей.
|
||
1. Задачи выполняются в порядке создания
|
||
Нет, задачи могут выполняться в любом порядке
|
||
2. Быстрее переключение между задачами
|
||
Да, реализация переключения при кооперативной многозадачности проще и быстрее, чем при вытесняющей
|
||
3. Идеальная предсказуемость времени выполнения задачи
|
||
Нет, кооперативная многозадачность не гарантирует идеальную предсказуемость выполнения. Более того, если одна из других задач будет долго исполняться, выполнение задачи тоже затянется
|
||
4. Из-за одной неправильной задачи случайно не остановятся другие
|
||
Нет, это преимущество вытесняющей многозадачности
|
||
5. Быстрее безопасное обращение к памяти
|
||
Да, благодаря предсказуемости момента переключения, задача успеет закончить свое обращение к памят
|
||
|
||
## Синтаксис async/await
|
||
Для кооперативной многозадачности в самих задачах нужна логика для передачи управления. Для этого в rust существует синтаксис async/await, он выглядит так:
|
||
```rust
|
||
// создание асинхронной функции
|
||
async fn task1() {
|
||
do_something().await; // вызов асинхроной функции и ожидание результата от нее
|
||
}
|
||
```
|
||
Сам по себе вызов такой функции (к примеру, просто `do_something()`) ничего не сделает. Для вызова асинхронной функции нужно использовать .await, если вызывать из асинхронной функции, либо вызывать из рантайма.
|
||
Для асинхронных замыканий тоже добавляется ключевое слово async:
|
||
```rust
|
||
let closure = async || {
|
||
do_something().await;
|
||
}
|
||
```
|
||
Или, если нет аргументов, можно опустить `||`:
|
||
```rust
|
||
let closure = async {
|
||
do_something().await;
|
||
}
|
||
```
|
||
|
||
Квиз-Одиночный выбор
|
||
Что выведет данный код:
|
||
```rust
|
||
fn main() {
|
||
async {
|
||
|
||
}
|
||
}
|
||
```
|
||
1. Не скомпилируется
|
||
2. чтото
|
||
3. Ничего
|
||
4. другое что-то
|
||
|
||
В rust нет встроенного рантайма, они все реализованы в виде библиотек. Самыми популярными являются tokio и smol (раньше еще был async-std, но он теперь discontinued). В данном уроке остановимся на tokio, так как он самый популярный.
|
||
Чтобы запустить асинхронный код с помошью tokio, можно использовать макрос tokio::main
|
||
```rust
|
||
#[tokio::main]
|
||
async fn main() {
|
||
do_something().await;
|
||
}
|
||
```
|
||
По стандартным настройкам этом макрос cоздает многопоточный рантайм, поэтому он эквивалентен такому коду:
|
||
```rust
|
||
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
|
||
Многопоточность в макросе указать явно можно так:
|
||
```rust
|
||
#[tokio::main(flavor = "multi_thread")]
|
||
async fn main() {
|
||
do_something().await;
|
||
}
|
||
```
|
||
Так же в макросе можно указать количество потоков, на которых будут запускаться асинхронные функции (по умолчанию их столько же, сколько потоков в процессоре в системе):
|
||
```rust
|
||
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
|
||
async fn main() {
|
||
//...
|
||
}
|
||
```
|
||
То же самое, но без макроса:
|
||
```rust
|
||
fn main() {
|
||
tokio::runtime::Builder::new_multi_thread()
|
||
.worker_threads(8)
|
||
.enable_all()
|
||
.build()
|
||
.unwrap()
|
||
.block_on(async {
|
||
//...
|
||
})
|
||
}
|
||
```
|
||
Чтобы рантайм был однопоточным, можно в макросе указать параметр flavor:
|
||
```rust
|
||
#[tokio::main(flavor = "current_thread")]
|
||
async fn main() {
|
||
//...
|
||
}
|
||
```
|
||
То же самое, но без макроса:
|
||
```rust
|
||
fn main() {
|
||
tokio::runtime::Builder::new_current_thread()
|
||
.enable_all()
|
||
.build()
|
||
.unwrap()
|
||
.block_on(async {
|
||
//...
|
||
})
|
||
}
|
||
```
|
||
|
||
Квиз-Одиночный выбор
|
||
На скольких потоках будут запускаться асинхронные функции, если просто использовать макрос `#[tokio::main]`:
|
||
1. 1
|
||
2. ошибка, нужно указать явно
|
||
3. 8
|
||
4. сколько потоков в процессоре на данной системе
|
||
|
||
|