Модуль runtime
Доступно только с флагом функции rt.
Среда выполнения Tokio.
В отличие от других программ на Rust, асинхронные приложения требуют поддержки среды выполнения. В частности, необходимы следующие службы среды выполнения:
- Цикл событий ввода-вывода, называемый драйвером, который управляет ресурсами ввода-вывода и распределяет события ввода-вывода между задачами, которые от них зависят.
- Планировщик для выполнения задач, использующих эти ресурсы ввода-вывода.
- Таймер для планирования работы, которая должна выполниться через заданный промежуток времени.
Runtime Tokio объединяет все эти службы в один тип, позволяя запускать, останавливать и настраивать их вместе. Однако часто не требуется настраивать Runtime вручную, и пользователь может просто использовать атрибут-макрос tokio::main, который создает Runtime "под капотом".
Выбор среды выполнения
Вот эмпирические правила для выбора правильной среды выполнения для вашего приложения:
+------------------------------------------------------+
| Нужен ли планировщик с work-stealing или многопоточный? |
+------------------------------------------------------+
| Да | Нет
| |
| |
v |
+------------------------+ |
| Многопоточный Runtime | |
+------------------------+ |
|
V
+--------------------------------+
| Выполняете ли вы `!Send` Future? |
+--------------------------------+
| Да | Нет
| |
V |
+--------------------------+ |
| Local Runtime (нестабильный) | |
+--------------------------+ |
|
v
+------------------------+
| Текущий поток Runtime |
+------------------------+
Приведенное дерево решений не является исчерпывающим. Существуют другие факторы, которые могут повлиять на ваше решение.
Связывание с синхронным кодом
Подробности см. на https://tokio.rs/tokio/topics/bridging.
Осведомленность о NUMA
Среда выполнения Tokio не осведомлена о NUMA (Non-Uniform Memory Access). Для лучшей производительности на системах с NUMA вы можете захотеть запустить несколько сред выполнения вместо одной.
Использование
Когда точная настройка не требуется, можно использовать атрибут-макрос tokio::main.
use tokio::net::TcpListener; use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let listener = TcpListener::bind("127.0.0.1:8080").await?; loop { let (mut socket, _) = listener.accept().await?; tokio::spawn(async move { let mut buf = [0; 1024]; // В цикле читаем данные из сокета и записываем данные обратно. loop { let n = match socket.read(&mut buf).await { // сокет закрыт Ok(0) => return, Ok(n) => n, Err(e) => { println!("не удалось прочитать из сокета; err = {:?}", e); return; } }; // Записываем данные обратно if let Err(e) = socket.write_all(&buf[0..n]).await { println!("не удалось записать в сокет; err = {:?}", e); return; } } }); } }
Из контекста среды выполнения дополнительные задачи порождаются с помощью функции tokio::spawn. Future, порожденные с помощью этой функции, будут выполняться в том же пуле потоков, который используется Runtime.
Экземпляр Runtime также может использоваться напрямую.
use tokio::net::TcpListener; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::runtime::Runtime; fn main() -> Result<(), Box<dyn std::error::Error>> { // Создаем среду выполнения let rt = Runtime::new()?; // Порождаем корневую задачу rt.block_on(async { let listener = TcpListener::bind("127.0.0.1:8080").await?; loop { let (mut socket, _) = listener.accept().await?; tokio::spawn(async move { let mut buf = [0; 1024]; // В цикле читаем данные из сокета и записываем данные обратно. loop { let n = match socket.read(&mut buf).await { // сокет закрыт Ok(0) => return, Ok(n) => n, Err(e) => { println!("не удалось прочитать из сокета; err = {:?}", e); return; } }; // Записываем данные обратно if let Err(e) = socket.write_all(&buf[0..n]).await { println!("не удалось записать в сокет; err = {:?}", e); return; } } }); } }) }
Конфигурации среды выполнения
Tokio предоставляет несколько стратегий планирования задач, подходящих для разных приложений. Для выбора планировщика можно использовать построитель среды выполнения или атрибут #[tokio::main].
Многопоточный планировщик
Многопоточный планировщик выполняет future в пуле потоков, используя стратегию work-stealing. По умолчанию он запускает рабочий поток для каждого доступного ядра CPU в системе. Это, как правило, идеальная конфигурация для большинства приложений. Многопоточный планировщик требует флага функции rt-multi-thread и выбирается по умолчанию:
#![allow(unused)] fn main() { use tokio::runtime; let threaded_rt = runtime::Runtime::new()?; }
Большинство приложений должны использовать многопоточный планировщик, за исключением некоторых узких случаев использования, таких как когда требуется выполнение только в одном потоке.
Планировщик текущего потока
Планировщик текущего потока предоставляет однопоточный исполнитель future. Все задачи будут создаваться и выполняться в текущем потоке. Это требует флага функции rt.
#![allow(unused)] fn main() { use tokio::runtime; let rt = runtime::Builder::new_current_thread() .build()?; }
Драйверы ресурсов
При ручной настройке среды выполнения никакие драйверы ресурсов не включены по умолчанию. В этом случае попытка использования сетевых типов или типов времени завершится неудачей. Чтобы включить эти типы, необходимо включить драйверы ресурсов. Это делается с помощью Builder::enable_io и Builder::enable_time. В качестве сокращения Builder::enable_all включает оба драйвера ресурсов.
Время жизни порожденных потоков
Среда выполнения может порождать потоки в зависимости от ее конфигурации и использования. Многопоточный планировщик порождает потоки для планирования задач и для вызовов spawn_blocking.
Пока Runtime активен, потоки могут завершаться после периодов простоя. После удаления Runtime все потоки среды выполнения обычно завершаются, но при наличии непрекращаемой порожденной работы не гарантируется, что они были завершены. Подробнее см. документацию на уровне структуры.
Подробное поведение среды выполнения
Этот раздел дает более подробную информацию о том, как среда выполнения Tokio будет планировать задачи для выполнения.
На самом базовом уровне среда выполнения имеет коллекцию задач, которые нужно запланировать. Она будет повторно удалять задачу из этой коллекции и планировать ее (вызывая poll). Когда коллекция пуста, поток перейдет в спящий режим до тех пор, пока в коллекцию не будет добавлена задача.
Однако вышесказанного недостаточно для гарантии корректного поведения среды выполнения. Например, среда выполнения может иметь одну задачу, которая всегда готова к планированию, и планировать эту задачу каждый раз. Это проблема, потому что это "морит голодом" другие задачи, не планируя их. Чтобы решить эту проблему, Tokio предоставляет следующую гарантию честности:
Если общее количество задач не растет безгранично и никакая задача не блокирует поток, то гарантируется, что задачи планируются честно.
Или, более формально:
При следующих двух предположениях:
- Существует некоторое число
MAX_TASKS, такое что общее количество задач в среде выполнения в любой конкретный момент времени никогда не превышаетMAX_TASKS.- Существует некоторое число
MAX_SCHEDULE, такое что вызовpollдля любой задачи, порожденной в среде выполнения, возвращается в течениеMAX_SCHEDULEединиц времени.Тогда существует некоторое число
MAX_DELAY, такое что когда задача пробуждается, она будет запланирована средой выполнения в течениеMAX_DELAYединиц времени.
(Здесь MAX_TASKS и MAX_SCHEDULE могут быть любыми числами, и пользователь среды выполнения может выбрать их. Число MAX_DELAY контролируется средой выполнения и зависит от значения MAX_TASKS и MAX_SCHEDULE.)
Помимо приведенной выше гарантии честности, нет никаких гарантий относительно порядка, в котором задачи планируются. Также нет гарантии, что среда выполнения одинаково честна ко всем задачам. Например, если у среды выполнения есть две задачи A и B, которые обе готовы, то среда выполнения может запланировать A пять раз, прежде чем запланирует B. Это верно, даже если A уступает с помощью yield_now. Все, что гарантируется, это то, что она запланирует B в конечном итоге.
Обычно задачи планируются только если они были пробуждены вызовом wake на их waker. Однако это не гарантируется, и Tokio может планировать задачи, которые не были пробуждены, при некоторых обстоятельствах. Это называется ложным пробуждением (spurious wakeup).
Ввод-вывод и таймеры
Помимо планирования задач, среда выполнения также должна управлять ресурсами ввода-вывода и таймерами. Она делает это, периодически проверяя, есть ли какие-либо ресурсы ввода-вывода или таймеры, которые готовы, и пробуждая соответствующую задачу, чтобы она была запланирована.
Эти проверки выполняются периодически между планированием задач. При тех же предположениях, что и предыдущая гарантия честности, Tokio гарантирует, что она пробудит задачи с событием ввода-вывода или таймера в течение некоторого максимального количества единиц времени.
Реэкспорт
#![allow(unused)] fn main() { pub use dump::Dump; // `tokio_unstable` и `taskdump` и `Linux` и (`AArch64` или `x86` или `x86-64`) }
Модули
| Имя | Флаги | Описание |
|---|---|---|
dump | tokio_unstable и taskdump и Linux и (AArch64 или x86 или x86-64) | Снимки состояния среды выполнения. |
Структуры
| Имя | Флаги | Описание |
|---|---|---|
Builder | Строит Tokio Runtime с пользовательскими значениями конфигурации. | |
EnterGuard | Защита контекста среды выполнения. | |
Handle | Дескриптор среды выполнения. | |
HistogramConfiguration | tokio_unstable | Конфигурация для гистограммы количества опросов. |
Id | tokio_unstable | Непрозрачный ID, однозначно идентифицирующий среду выполнения относительно всех других текущих сред выполнения. |
LocalOptions | tokio_unstable | Опции конфигурации только для LocalRuntime. |
LocalRuntime | tokio_unstable | Локальная среда выполнения Tokio. |
LogHistogram | tokio_unstable | Логарифмическая гистограмма. |
LogHistogramBuilder | tokio_unstable | Конфигурация для LogHistogram. |
RngSeed | tokio_unstable | Seed для генерации случайных чисел. |
Runtime | Среда выполнения Tokio. | |
RuntimeMetrics | Дескриптор метрик среды выполнения. | |
TaskMeta | tokio_unstable | Метаданные задачи, предоставляемые пользовательскими хуками для событий задач. |
TryCurrentError | Ошибка, возвращаемая try_current, когда среда выполнения не запущена. |
Перечисления
| Имя | Флаги | Описание |
|---|---|---|
HistogramScale | tokio_unstable | Использует ли гистограмма для агрегации метрики линейную или логарифмическую шкалу. |
InvalidHistogramConfiguration | tokio_unstable | Ошибка построения гистограммы. |
RuntimeFlavor | Тип (flavor) среды выполнения. | |
UnhandledPanic | tokio_unstable | Как среда выполнения должна реагировать на необработанные паники. |