Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Крейт tokio

Источник

Сайт проекта https://tokio.rs/

Среда выполнения для создания надежных сетевых приложений без компромиссов в скорости.

Tokio — это событийно-ориентированная, неблокирующая платформа ввода-вывода для написания асинхронных приложений на языке Rust. На высоком уровне она предоставляет несколько основных компонентов:

  • Инструменты для работы с асинхронными задачами, включая примитивы синхронизации, каналы, таймауты, задержки и интервалы.
  • API для выполнения асинхронного ввода-вывода, включая TCP- и UDP-сокеты, операции с файловой системой, а также управление процессами и сигналами.
  • Среду выполнения для исполнения асинхронного кода, включая планировщик задач, драйвер ввода-вывода, использующий системные очереди событий (epoll, kqueue, IOCP и т.д.), и высокопроизводительный таймер.

Документация на уровне руководства находится на веб-сайте.

Обзор Tokio

Tokio состоит из ряда модулей, которые предоставляют функциональность, необходимую для реализации асинхронных приложений на Rust. В этом разделе мы кратко рассмотрим Tokio, резюмируя основные API и их назначение.

Самый простой способ начать — включить все функции. Для этого используйте флаг full:

tokio = { version = "1", features = ["full"] }

Создание приложений

Tokio отлично подходит для написания приложений, и большинству пользователей в этом случае не стоит слишком беспокоиться о выборе функций. Если вы не уверены, мы рекомендуем использовать full, чтобы избежать препятствий в процессе разработки.

Пример

Этот пример показывает самый быстрый способ начать работу с Tokio.

tokio = { version = "1", features = ["full"] }

Создание библиотек

Как автор библиотеки, вы должны стремиться предоставить максимально легковесный крейт, основанный на Tokio. Для этого следует включать только те функции, которые вам необходимы. Это позволяет пользователям использовать ваш крейт без необходимости включать лишние функции.

Пример

Этот пример показывает, как можно импортировать функции для библиотеки, которой нужно только tokio::spawn и TcpStream.

tokio = { version = "1", features = ["rt", "net"] }

Работа с задачами

Асинхронные программы в Rust основаны на легковесных, неблокирующих единицах выполнения, называемых задачами. Модуль tokio::task предоставляет важные инструменты для работы с задачами:

  • Функцию spawn и тип JoinHandle для планирования новой задачи в среде выполнения Tokio и ожидания результата выполненной задачи соответственно.
  • Функции для запуска блокирующих операций в контексте асинхронной задачи.

Модуль tokio::task доступен только при включенном флаге функции "rt".

Модуль tokio::sync содержит примитивы синхронизации для обмена данными. К ним относятся:

  • Каналы (oneshot, mpsc, watch и broadcast) для отправки значений между задачами.
  • Неблокирующий Mutex для контроля доступа к разделяемому, изменяемому значению.
  • Асинхронный тип Barrier для синхронизации нескольких задач перед началом вычислений.

Модуль tokio::sync доступен только при включенном флаге функции "sync".

Модуль tokio::time предоставляет утилиты для отслеживания времени и планирования работы. Это включает функции для установки таймаутов для задач, откладывания выполнения на будущее или повторения операции через интервалы.

Для использования tokio::time должен быть включен флаг функции "time".

Наконец, Tokio предоставляет среду выполнения для исполнения асинхронных задач. Большинство приложений могут использовать макрос #[tokio::main] для запуска своего кода в среде выполнения Tokio. Однако этот макрос предоставляет только базовые опции конфигурации. В качестве альтернативы, модуль tokio::runtime предоставляет более мощные API для настройки и управления средами выполнения. Вам следует использовать этот модуль, если макрос #[tokio::main] не предоставляет нужной вам функциональности.

Использование среды выполнения требует флагов функций "rt" или "rt-multi-thread" для включения однопоточного планировщика для текущего потока и многопоточного планировщика соответственно. Подробности см. в документации к модулю runtime. Кроме того, флаг функции "macros" включает атрибуты #[tokio::main] и #[tokio::test].

CPU-ограниченные задачи и блокирующий код

Tokio может параллельно выполнять множество задач в нескольких потоках, постоянно переключая выполняемую в данный момент задачу в каждом потоке. Однако такая перестановка может происходить только в точках .await, поэтому код, который долгое время работает без достижения .await, будет блокировать выполнение других задач. Для решения этой проблемы Tokio предоставляет два вида потоков: Основные потоки и Блокирующие потоки.

Основные потоки — это место, где выполняется весь асинхронный код, и по умолчанию Tokio создает по одному такому потоку на каждое ядро CPU. Вы можете использовать переменную окружения TOKIO_WORKER_THREADS для переопределения значения по умолчанию.

Блокирующие потоки создаются по требованию, могут использоваться для запуска блокирующего кода, который в противном случае блокировал бы выполнение других задач, и сохраняются alive в течение некоторого времени бездействия, которое можно настроить с помощью thread_keep_alive. Поскольку Tokio не может вытеснять блокирующие задачи, как это можно делать с асинхронным кодом, верхний предел количества блокирующих потоков очень велик. Эти ограничения можно настроить в Builder.

Для порождения блокирующей задачи следует использовать функцию spawn_blocking.

#[tokio::main]
async fn main() {
    // Этот код выполняется в основном потоке.

    let blocking_task = tokio::task::spawn_blocking(|| {
        // Этот код выполняется в блокирующем потоке.
        // Здесь блокировка допустима.
    });

    // Мы можем ждать завершения блокирующей задачи так:
    // Если блокирующая задача запаникует, unwrap ниже распространит панику.
    blocking_task.await.unwrap();
}

Если ваш код ограничен производительностью CPU и вы хотите ограничить количество потоков для его выполнения, вам следует использовать отдельный пул потоков, предназначенный для CPU-ограниченных задач. Например, вы можете рассмотреть возможность использования библиотеки rayon для таких задач. Также можно создать дополнительную среду выполнения Tokio, предназначенную для CPU-ограниченных задач, но в этом случае следует убедиться, что эта среда выполнения выполняет только CPU-ограниченные задачи, так как задачи, ограниченные вводом-выводом, в этой среде будут работать плохо.

Подсказка: При использовании rayon вы можете использовать канал oneshot для отправки результата обратно в Tokio по завершении задачи rayon.

Асинхронный ввод-вывод

Помимо планирования и выполнения задач, Tokio предоставляет все необходимое для асинхронного выполнения ввода и вывода.

Модуль tokio::io предоставляет основные асинхронные примитивы ввода-вывода Tokio: трейты AsyncRead, AsyncWrite и AsyncBufRead. Кроме того, при включенном флаге функции "io-util" он также предоставляет комбинаторы и функции для работы с этими трейтами, образуя асинхронный аналог std::io.

Tokio также включает API для выполнения различных видов ввода-вывода и асинхронного взаимодействия с операционной системой. К ним относятся:

  • tokio::net — содержит неблокирующие версии TCP, UDP и Unix Domain Sockets (включается флагом "net").
  • tokio::fs — аналогичен std::fs, но для асинхронного выполнения операций с файловой системой (включается флагом "fs").
  • tokio::signal — для асинхронной обработки сигналов ОС Unix и Windows (включается флагом "signal").
  • tokio::process — для порождения и управления дочерними процессами (включается флагом "process").

Примеры

Простой TCP-эхо сервер:

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) => {
                        eprintln!("не удалось прочитать из сокета; err = {:?}", e);
                        return;
                    }
                };

                // Записываем данные обратно
                if let Err(e) = socket.write_all(&buf[0..n]).await {
                    eprintln!("не удалось записать в сокет; err = {:?}", e);
                    return;
                }
            }
        });
    }
}

Флаги функций

Tokio использует набор флагов функций для уменьшения количества компилируемого кода. Можно включать только определенные функции. По умолчанию Tokio не включает никакие функции, но позволяет включить их подмножество для конкретного случая использования. Ниже приведен список доступных флагов функций. Вы также можете заметить, что над каждой функцией, структурой и трейтом указан один или несколько флагов функций, необходимых для использования этого элемента. Если вы новичок в Tokio, рекомендуется использовать флаг full, который включает все публичные API. Однако имейте в виду, что это потянет за собой множество дополнительных зависимостей, которые могут вам не понадобиться.

  • full: Включает все функции, перечисленные ниже, кроме test-util и tracing.
  • rt: Включает tokio::spawn, планировщик для текущего потока и утилиты, не связанные с планировщиком.
  • rt-multi-thread: Включает более тяжелый многопоточный планировщик с work-stealing.
  • io-util: Включает трейты-расширения на основе ввода-вывода.
  • io-std: Включает типы Stdout, Stdin и Stderr.
  • net: Включает типы tokio::net, такие как TcpStream, UnixStream и UdpSocket, а также (в Unix-подобных системах) AsyncFd и (в FreeBSD) PollAio.
  • time: Включает типы tokio::time и позволяет планировщикам включить встроенный таймер.
  • process: Включает типы tokio::process.
  • macros: Включает макросы #[tokio::main] и #[tokio::test].
  • sync: Включает все типы tokio::sync.
  • signal: Включает все типы tokio::signal.
  • fs: Включает типы tokio::fs.
  • test-util: Включает инфраструктуру для тестирования среды выполнения Tokio.
  • parking_lot: В качестве потенциальной оптимизации использует примитивы синхронизации из крейта parking_lot внутри. Также эта зависимость необходима для создания некоторых наших примитивов в контексте const. MSRV может увеличиваться в соответствии с используемой версией parking_lot.

Примечание: Трейты AsyncRead и AsyncWrite не требуют никаких флагов и всегда доступны.

Нестабильные функции

Некоторые флаги функций доступны только при указании флага tokio_unstable:

  • tracing: Включает события tracing.

Аналогично, некоторые части API доступны только с тем же флагом:

  • task::Builder
  • Некоторые методы в task::JoinSet
  • runtime::RuntimeMetrics
  • runtime::Builder::on_task_spawn
  • runtime::Builder::on_task_terminate
  • runtime::Builder::unhandled_panic
  • runtime::TaskMeta

Этот флаг включает нестабильные функции. Их публичный API может нарушать семантическое версионирование в релизах 1.x. Чтобы включить эти функции, аргумент --cfg tokio_unstable должен быть передан в rustc при компиляции. Это позволяет явно согласиться на использование функций, которые могут нарушать соглашения semver, поскольку Cargo пока не поддерживает такие соглашения напрямую.

Вы можете указать это в файле .cargo/config.toml вашего проекта:

[build]
rustflags = ["--cfg", "tokio_unstable"]

Раздел [build] указывается не в файле Cargo.toml, а в конфигурационном файле Cargo .cargo/config.toml.

Альтернативно, вы можете указать это с помощью переменной окружения:

# Многие *nix-оболочки:
export RUSTFLAGS="--cfg tokio_unstable"
cargo build

# Windows PowerShell:
$Env:RUSTFLAGS="--cfg tokio_unstable"
cargo build

Поддерживаемые платформы

Tokio в настоящее время гарантированно поддерживает следующие платформы:

  • Linux
  • Windows
  • Android (уровень API 21)
  • macOS
  • iOS
  • FreeBSD

Tokio будет продолжать поддерживать эти платформы в будущем. Однако будущие релизы могут изменить требования, такие как минимальная требуемая версия libc в Linux, уровень API в Android или поддерживаемая версия FreeBSD.

Помимо указанных выше платформ, Tokio предназначен для работы на всех платформах, поддерживаемых крейтом mio. Более длинный список можно найти в документации mio. Однако эти дополнительные платформы могут стать неподдерживаемыми в будущем.

Обратите внимание, что Wine считается платформой, отличной от Windows. Подробнее о поддержке Wine см. в документации mio.

Поддержка WASM

Tokio имеет ограниченную поддержку платформы WASM. Без флага tokio_unstable поддерживаются следующие функции:

  • sync
  • macros
  • io-util
  • rt
  • time

Включение любой другой функции (включая full) приведет к ошибке компиляции.

Модуль time будет работать только на WASM-платформах, которые поддерживают таймеры (например, wasm32-wasi). Функции времени вызовут панику, если используются на WASM-платформе без поддержки таймеров.

Также обратите внимание, что если среда выполнения становится бесконечно бездействующей, она немедленно запаникует вместо вечной блокировки. На платформах, которые не поддерживают время, это означает, что среда выполнения никогда не может простаивать.

Нестабильная поддержка WASM

Tokio также имеет нестабильную поддержку некоторых дополнительных WASM-функций. Это требует использования флага tokio_unstable.

Использование этого флага позволяет использовать tokio::net для цели wasm32-wasi. Однако не все методы доступны для сетевых типов, поскольку WASI в настоящее время не поддерживает создание новых сокетов изнутри WASM. Из-за этого сокеты в настоящее время должны создаваться с помощью трейта FromRawFd.

Реэкспорты

#![allow(unused)]
fn main() {
pub use task::spawn; // rt
}

Модули

ИмяФлагиОписание
fsfsАсинхронные файловые утилиты.
ioТрейты, вспомогательные средства и определения типов для асинхронной функциональности ввода-вывода.
netnetПривязки TCP/UDP/Unix для tokio.
processprocessРеализация асинхронного управления процессами для Tokio.
runtimertСреда выполнения Tokio.
signalsignalАсинхронная обработка сигналов для Tokio.
syncsyncПримитивы синхронизации для использования в асинхронных контекстах.
taskrtАсинхронные "зеленые потоки" (задачи).
timetimeУтилиты для отслеживания времени.

Макросы

ИмяФлагиОписание
joinmacrosОжидает завершения нескольких параллельных ветвей.
pinЗакрепляет значение в стеке.
selectmacrosОжидает завершения первой из нескольких параллельных ветвей, отменяя остальные.
task_localrtОбъявляет новый задачно-локальный ключ типа tokio::task::LocalKey.
try_joinmacrosОжидает успешного завершения всех параллельных ветвей или первой ошибки.

Атрибут-макросы

ИмяФлагиОписание
mainrt и macrosПомечает асинхронную функцию для выполнения выбранной средой выполнения. Помогает настроить Runtime без прямого использования Runtime или Builder.
testrt и macrosПомечает асинхронную функцию для выполнения средой выполнения, подходящей для тестов. Помогает настроить Runtime без прямого использования Runtime или Builder.