==Умеет порождать конкурентные задачи через 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 будет запускать задачи на стольких потоках, сколько потоков у процессора в системе Столько, сколько потоков в процессоре на данной системе Правильно, без явного указания потоков будет столько же, сколько их в процессоре в системе