11 KiB
Кооперативная Вытесняющая - пример: процессор преимуще
для синтаксиса - объяснить, что вызов функции не исполняет ее сразу, как в js, а возвращает (пока что) какое-то значение, которое исполнит функцию по вызову .await на ней
макрос - привести пример #[tokio::main] и #[tokio::main(...)]
Start:
Допустим, перед вами стоит несколько задач, которые надо выполнить параллельно, но фокусироваться в один момент вы можете только одной задаче. По какой стратегии вы будете переключаться между этими задачами?
Кооперативная и вытесняющая многозадачность
Одним из вариантов, который может прийти в голову, это сделать небольшой прогресс в одной задаче, потом переключиться на вторую, сделать в ней небольшой прогресс, и так далее. Такая многозадачность, когда исполнитель решает момент, когда нужно переключаться между задачами, называется вытесняющей (так как одна задача как бы вытесняет другую). По такому принципу распределяют задачи на процессор все современные десктопные ОС. А теперь представьте, что момент переключения на другую задачу решаете не вы, а сама задача. Такая многозадачность называется кооперативной (так как задачам приходится кооперировать друг с другом за процессорное время).
Квиз-Одиночный выбор В чем отличие кооперативной многозадачности от вытесняющей?
- Исполнитель решает, когда переключаться между задачами Нет, в кооперативной многозадачности момент переключения решают задачи, а не исполнитель
- Задачи переключаются по истечению времени Нет, задачи могут выполняться сколько угодно
- Задачи решают, когда можно переключиться с них Правильно, только задачи решают, когда переключаться и могут выполняться сколько угодно
- Все задачи выполняются только в один поток Нет, такого требования к кооперативности нет
Квиз-Множественный выбор У каких из этих задач кооперативная многозадачность?
- Потоки процессов на Linux
- У модема по adsl для звонков и интернета (пользоваться интернетом, пока идет звонок, нельзя)
- Пер
- Очередь на печать на принтере
- Обработка
Многозадачность в асинхронном rust
В rust для асинхронного кода выбрана кооперативная многозадачность. Такой выбор обоснован тем, что переключение между задачами в кооперативной многозадачности существенно быстрее, и дешевле по производительности обходится безопасность по памяти. Если бы многозадачность была вытесняющей, то при переключении задач пришлось бы выполнять дополнительные действия для сохранения состояния задачи, в то время как в кооперативной задачи сами отвечают за свое состояние. При этом у кооперативной многозадачности есть и недостатки: если задача зависает, то другие задачи не могут выполняться. Поэтому во всех современных десктопных ОС выбрана именно вытесняющая многозадачность, ведь иначе какой-нибудь процесс случайно или злонамеренно мог бы повесить всю систему. И поэтому при написании асинхронных функций нужно следить за тем, как долго они выполняются, и если возникает хоть немного занимающая время задача, ее нужно выносить в отдельный поток (в рантаймах есть встроенные инструменты для этого)
Квиз-Множественный выбор Выберите преимущества кооперативной многозадачности перед вытесняющей.
- Задачи выполняются в порядке создания Нет, задачи могут выполняться в любом порядке
- Быстрее переключение между задачами Да, реализация переключения при кооперативной многозадачности проще и быстрее, чем при вытесняющей
- Идеальная предсказуемость времени выполнения задачи Нет, кооперативная многозадачность не гарантирует идеальную предсказуемость выполнения. Более того, если одна из других задач будет долго исполняться, выполнение задачи тоже затянется
- Из-за одной неправильной задачи случайно не остановятся другие Нет, это преимущество вытесняющей многозадачности
- Быстрее безопасное обращение к памяти Да, благодаря предсказуемости момента переключения, задача успеет закончить свое обращение к памят
Синтаксис 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 {
}
}
- Не скомпилируется
- чтото
- Ничего
- другое что-то
В 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
- ошибка, нужно указать явно
- 8
- сколько потоков в процессоре на данной системе