Модуль runtime
rt.Среда выполнения Tokio.
В отличие от других программ на Rust, асинхронным приложениям требуется поддержка среды выполнения. В частности, необходимы следующие службы среды выполнения:
- Цикл событий ввода-вывода, называемый драйвером, который управляет ресурсами ввода-вывода и отправляет события ввода-вывода задачам, зависящим от этих ресурсов.
- Планировщик для выполнения задач, использующих эти ресурсы ввода-вывода.
- Таймер для планирования запуска задач по истечении заданного промежутка времени.
Runtime Tokio объединяет все эти службы в один тип, позволяя запускать, останавливать и настраивать их вместе. Однако часто нет необходимости вручную настраивать Runtime, и пользователь может просто использовать макрос атрибута tokio::main, который создаёт Runtime под капотом.
Выбор среды выполнения
Вот несколько практических правил, которые помогут выбрать подходящую среду выполнения для вашего приложения.
+------------------------------------------------------+
| Do you want work-stealing or multi-thread scheduler? |
+------------------------------------------------------+
| Yes | No
| |
| |
v |
+------------------------+ |
| Multi-threaded Runtime | |
+------------------------+ |
|
V
+--------------------------------+
| Do you execute `!Send` Future? |
+--------------------------------+
| Yes | No
| |
V |
+---------------+ |
| Local Runtime | |
+---------------+ |
|
v
+------------------------+
| Current-thread Runtime |
+------------------------+Приведённая выше схема принятия решений не охватывает все случаи. На ваш выбор могут повлиять и другие факторы.
Взаимодействие с синхронным кодом
Подробности см. на странице https://tokio.rs/tokio/topics/bridging.
Поддержка NUMA
Среда выполнения tokio не учитывает NUMA (неоднородный доступ к памяти). Для повышения производительности в системах 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];
// In a loop, read data from the socket and write the data back.
loop {
let n = match socket.read(&mut buf).await {
// socket closed
Ok(0) => return,
Ok(n) => n,
Err(e) => {
println!("failed to read from socket; err = {:?}", e);
return;
}
};
// Write the data back
if let Err(e) = socket.write_all(&buf[0..n]).await {
println!("failed to write to socket; err = {:?}", e);
return;
}
}
});
}
}В контексте среды выполнения дополнительные задачи запускаются с помощью функции tokio::spawn. Фьючерсы, запущенные с помощью этой функции, будут выполняться в том же пуле потоков, что и Runtime.
Экземпляр Runtime также можно использовать напрямую.
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::runtime::Runtime;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create the runtime
let rt = Runtime::new()?;
// Spawn the root task
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];
// In a loop, read data from the socket and write the data back.
loop {
let n = match socket.read(&mut buf).await {
// socket closed
Ok(0) => return,
Ok(n) => n,
Err(e) => {
println!("failed to read from socket; err = {:?}", e);
return;
}
};
// Write the data back
if let Err(e) = socket.write_all(&buf[0..n]).await {
println!("failed to write to socket; err = {:?}", e);
return;
}
}
});
}
})
}Конфигурации среды выполнения
Tokio предоставляет несколько стратегий планирования задач, подходящих для различных приложений. Чтобы выбрать планировщик, можно использовать построитель среды выполнения или атрибут #[tokio::main].
Многопоточный планировщик
Многопоточный планировщик выполняет фьючерсы в пуле потоков, используя стратегию кражи задач. По умолчанию он запускает рабочий поток для каждого доступного ядра процессора. Для большинства приложений это, как правило, оптимальная конфигурация. Для многопоточного планировщика требуется флаг функции rt-multi-thread, и он выбирается по умолчанию:
use tokio::runtime;
let threaded_rt = runtime::Runtime::new()?;Большинству приложений следует использовать многопоточный планировщик, за исключением некоторых особых случаев, например, когда необходимо запускать только один поток.
Планировщик текущего потока
Планировщик текущего потока предоставляет однопоточный исполнитель фьючерсов. Все задачи будут создаваться и выполняться в текущем потоке. Для этого требуется флаг функции rt.
use tokio::runtime;
let rt = runtime::Builder::new_current_thread()
.build()?;Драйверы ресурсов
При ручной настройке среды выполнения драйверы ресурсов по умолчанию отключены. В этом случае попытка использовать сетевые типы или типы времени завершится ошибкой. Чтобы включить эти типы, необходимо включить драйверы ресурсов. Для этого используются методы Builder::enable_io и Builder::enable_time. Для краткости можно вызвать Builder::enable_all, который включает оба драйвера ресурсов.
Запуск среды выполнения
Среда выполнения Tokio может выполнять задачи, только когда она запущена. Обычно это не проблема, поскольку среда выполнения в стандартной конфигурации всегда работает. Однако для альтернативных конфигураций, таких как среда выполнения текущего потока, необходимо вызвать Runtime::block_on.
- Многопоточная среда выполнения всегда работает, поскольку сама запускает рабочие потоки.
- Среда выполнения текущего потока не запускает рабочие потоки, поэтому она может выполнять задачи, только если предоставить ей поток, вызвав
Runtime::block_on. LocalSetвыполняет локальные задачи, запущенные в ней, только когдаLocalSetимеет значение.awaitedили когда её выполнение иным образом обеспечивается одним из предназначенных для этого методов.
Обратите внимание: Handle::block_on не обеспечивает работу среды выполнения. При использовании среды выполнения текущего потока необходимо хотя бы один раз вызвать Runtime::block_on. Вызова Handle::block_on недостаточно.
Время жизни запущенных потоков
В зависимости от конфигурации и использования среда выполнения может запускать потоки. Многопоточный планировщик запускает потоки для планирования задач и для вызовов spawn_blocking.
Пока Runtime активна, потоки могут завершаться после периодов бездействия. После удаления Runtime все потоки среды выполнения обычно завершаются, однако при наличии неостанавливаемой запущенной работы их завершение не гарантируется. Подробнее см. в документации уровня структуры.
fork в Unix
Пользовательский код, вызывающий fork(2) без немедленного вызова exec, не должен повторно использовать Tokio в дочернем процессе. Tokio поддерживает такой тип fork только в двух случаях:
- Fork происходит до того, как родительский процесс каким-либо образом использовал Tokio.
- Дочерний процесс не использует Tokio после fork.
Создание или использование среды выполнения Tokio в дочернем процессе после того, как родительский процесс использовал Tokio, не поддерживается, даже если в дочернем процессе создаётся новая среда выполнения. Некоторые модули Tokio, в том числе обработка процессов и сигналов, используют глобальное состояние процесса, которое в настоящее время невозможно сбросить после fork.
Подробное описание работы среды выполнения
В этом разделе подробнее описано, как среда выполнения 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 может запланировать задачи, которые не были пробуждены. Это называется ложным пробуждением.
Ввод-вывод и таймеры
Среда выполнения должна не только планировать задачи, но и управлять ресурсами ввода-вывода и таймерами. Для этого она периодически проверяет, готовы ли какие-либо ресурсы ввода-вывода или таймеры, и пробуждает соответствующую задачу, чтобы та была запланирована.
Эти проверки периодически выполняются между планированиями задач. При тех же предположениях, что и для приведённой выше гарантии справедливости, Tokio гарантирует, что пробудит задачи при событии ввода-вывода или таймера не более чем за некоторое максимальное число единиц времени.
Среда выполнения текущего потока (поведение на момент написания)
В этом разделе описано текущее поведение среды выполнения текущего потока. Это поведение может измениться в будущих версиях Tokio.
Среда выполнения текущего потока поддерживает две FIFO-очереди готовых к планированию задач: глобальную и локальную. Среда выполнения предпочитает выбирать следующую задачу из локальной очереди и выбирает задачу из глобальной очереди только в том случае, если локальная очередь пуста или если задача из локальной очереди была выбрана 31 раз подряд. Число 31 можно изменить с помощью параметра global_queue_interval.
Среда выполнения проверяет наличие новых событий ввода-вывода или таймера, когда нет готовых к планированию задач или когда подряд запланировано 61 задача. Число 61 можно изменить с помощью параметра event_interval.
Если задача пробуждается из другой задачи, выполняющейся в среде выполнения, пробуждённая задача сразу добавляется в локальную очередь. В противном случае задача добавляется в глобальную очередь. Среда выполнения текущего потока не использует оптимизацию слота LIFO.
Многопоточная среда выполнения (поведение на момент написания)
В этом разделе описано текущее поведение многопоточной среды выполнения. Это поведение может измениться в будущих версиях Tokio.
Многопоточная среда выполнения имеет фиксированное число рабочих потоков, которые создаются при запуске. Она поддерживает одну глобальную очередь и локальную очередь для каждого рабочего потока. Локальная очередь рабочего потока может содержать не более 256 задач. Если в локальную очередь добавляется больше 256 задач, половина из них перемещается в глобальную очередь, чтобы освободить место.
Среда выполнения предпочитает выбирать следующую задачу из локальной очереди и выбирает задачу из глобальной очереди только в том случае, если локальная очередь пуста или если задача из локальной очереди была выбрана global_queue_interval раз подряд. Если значение global_queue_interval явно не задано с помощью построителя среды выполнения, среда выполнения вычисляет его динамически, используя эвристику, нацеленную на интервал в 10 мс между проверками глобальной очереди (на основе метрики worker_mean_poll_time).
Если локальная и глобальная очереди пусты, рабочий поток пытается украсть задачи из локальной очереди другого рабочего потока. Кража выполняется путём перемещения половины задач из одной локальной очереди в другую.
Среда выполнения проверяет наличие новых событий ввода-вывода или таймера, когда нет готовых к планированию задач или когда подряд запланировано 61 задача. Число 61 можно изменить с помощью параметра event_interval.
Многопоточная среда выполнения использует оптимизацию слота LIFO: когда задача пробуждает другую задачу, последняя помещается в слот LIFO рабочего потока, а не в очередь. Если в этот момент слот LIFO уже занят задачей, его содержимое заменяется, а прежняя задача перемещается в локальную очередь потока. После завершения планирования задачи среда выполнения немедленно запланирует задачу из слота LIFO, если она там есть. При использовании слота LIFO бюджет кооперативного планирования не сбрасывается. Кроме того, если рабочий поток использует слот LIFO три раза подряд, его использование временно отключается, пока рабочий поток не запланирует задачу, которая поступила не из слота LIFO. Слот LIFO можно отключить с помощью параметра disable_lifo_slot. Слот LIFO отделён от локальной очереди, поэтому другие рабочие потоки не могут украсть находящуюся в нём задачу.
Если задача пробуждается из потока, который не является рабочим потоком, она помещается в глобальную очередь.
Настройка производительности
Предварительное увеличение таблицы файловых дескрипторов
В Linux увеличение таблицы файловых дескрипторов может приостанавливать рабочие потоки. См. пример prewarm-fd-table.
Повторные экспорты
-
pub use dump::Dump;tokio_unstableи Linux, а также при включённой функции cratetaskdumpи (AArch64, s390x, x86 или x86-64)
Модули
-
dump
tokio_unstableи Linux, а также при включённой функции cratetaskdumpи (AArch64, s390x, x86 или x86-64) - Снимки состояния среды выполнения.
Структуры
- Builder
- Создаёт среду выполнения Tokio с заданными значениями конфигурации.
- Enter
Guard - Защитный объект контекста среды выполнения.
- Handle
- Дескриптор среды выполнения.
-
Histogram
Configuration tokio_unstable - Конфигурация гистограммы количества вызовов poll
- Id
- Непрозрачный идентификатор, уникально определяющий среду выполнения среди всех остальных работающих в данный момент сред выполнения.
- Local
Options - Параметры конфигурации только для
LocalRuntime - Local
Runtime - Локальная среда выполнения Tokio.
-
LogHistogram
tokio_unstable - Логарифмическая гистограмма
-
LogHistogram
Builder tokio_unstable - Конфигурация для
LogHistogram -
RngSeed
tokio_unstable - Начальное значение для генерации случайных чисел.
- Runtime
- Среда выполнения Tokio.
- Runtime
Metrics - Дескриптор метрик среды выполнения.
-
Task
Meta tokio_unstable - Метаданные задачи, передаваемые предоставленным пользователем обработчикам событий задач.
- TryCurrent
Error - Ошибка, возвращаемая
try_current, если среда выполнения Runtime не была запущена
Перечисления
-
Histogram
Scale tokio_unstable - Определяет, использует ли гистограмма, применяемая для агрегирования метрики, линейную или логарифмическую шкалу.
-
Invalid
Histogram Configuration tokio_unstable - Ошибка при создании гистограммы
- Runtime
Flavor - Вариант конфигурации
Runtime. -
Unhandled
Panic tokio_unstable - Определяет, как среда выполнения должна реагировать на необработанные паники.
Функции
- is_
rt_ shutdown_ err - Проверяет, было ли указанное сообщение об ошибке выдано Tokio при завершении работы среды выполнения.
-
worker_
index tokio_unstable - Возвращает индекс текущего рабочего потока, если вызвана из рабочего потока среды выполнения.
MIT License
Copyright © Tokio Contributors
https://docs.rs/tokio/1.53.1/tokio/runtime/index.html