From d2bb57a8097d15b83f88c5e64e24db32591b300f Mon Sep 17 00:00:00 2001
From: TakiMoysha <36836047+TakiMoysha@users.noreply.github.com>
Date: Mon, 15 Sep 2025 20:36:15 +0200
Subject: [PATCH] completed translation of post 12
---
.../posts/12-async-await/index.ru.md | 82 +++++++++----------
1 file changed, 41 insertions(+), 41 deletions(-)
diff --git a/blog/content/edition-2/posts/12-async-await/index.ru.md b/blog/content/edition-2/posts/12-async-await/index.ru.md
index b6061ac5..45519b02 100644
--- a/blog/content/edition-2/posts/12-async-await/index.ru.md
+++ b/blog/content/edition-2/posts/12-async-await/index.ru.md
@@ -1038,7 +1038,7 @@ async fn example_task() {
- Т.к `example_task` напрямую возвращает `Poll::Ready`, она не добавляется обратно в очередь задач.
- Метод `run` возвращается после того, как `task_queue` становится пустым. Выполнение нашей функции `kernel_main` продолжается, и выводится сообщение _"It did not crash!"_.
-### Async Keyboard Input
+### Асинхронный ввод с клавиатуры
Наш простой исполнитель не использует уведомления `Waker` и просто циклически обрабатывает все задачи до тех пор, пока они не завершатся. Это не было проблемой для нашего примера, так как наш `example_task` может завершиться сразу при первом вызове `poll`. Чтобы увидеть преимущества производительности правильной реализации `Waker`, нам нужно сначала создать задачу, которая действительно асинхронна, т.е. задачу, которая, вероятно, вернёт `Poll::Pending` при первом вызове `poll`.
@@ -1050,19 +1050,19 @@ async fn example_task() {
#### Очередь Скан-кодов
-Сейчас мы обрабатываем ввод с клавиатуры непосредственно в обработчике прерываний. Это нехорошая реализация в долгосрочной перспективе, потому что обработчики прерываний должны быть как можно короче ( ), так как они могут прерывать важную работу. Вместо этого обработчики прерываний должны выполнять только минимальный объем необходимой работы (например, считывание кода сканирования клавиатуры) и оставлять остальную работу (например, интерпретацию кода сканирования) фоновой задаче.
+Сейчас мы обрабатываем ввод с клавиатуры непосредственно в обработчике прерываний. Это не лучший подход в долгосрочной перспективе, обработка прерываний должна выполняться как можно быстрее, так как они могут прерывать важную работу. Вместо этого обработчики прерываний должны выполнять только минимальный объем необходимой работы (например, считывание кода сканирования клавиатуры) и оставлять остальную работу (например, интерпретацию кода сканирования) фоновой задаче.
-Распространённым шаблоном для делегирования работы фоновым задачам является очередь. Обработчик прерываний добавляет единицы работы в очередь, а фоновая задача обрабатывает работу в очереди. Применительно к нашему прерыванию клавиатуры это означает, что обработчик прерываний только считывает скан-код с клавиатуры, добавляет его в очередь, а затем возвращается. Задача клавиатуры находится на другом конце очереди и интерпретирует и обрабатывает каждый скан-код, который в неё добавляется:
+Распространённым шаблоном для делегирования работы фоновым задачам является очередь. Обработчик прерываний добавляет единицы работы в очередь, а фоновая задача обрабатывает работу в очереди. Применительно к нашем прерываниям это означает, что обработчик прерываний только считывает скан-код с клавиатуры, добавляет его в очередь, а затем возвращается. Задача клавиатуры находится на другом конце очереди и интерпретирует и обрабатывает каждый скан-код, который в неё добавляется:

-Простая реализация такой очереди может быть основана на `VecDeque`, защищённом мьютексом. Однако использование мьютексов в обработчиках прерываний — не очень хорошая идея, так как это может легко привести к взаимным блокировкам (deadlock). Например, пользователь нажимает клавишу, но в тот же момент задача клавиатуру заблокировала очередь, обработчик прерываний пытается снова захватить блокировку и застревает навсегда. Ещё одна проблема с этим подходом в том, что `VecDeque` автоматически увеличивает свою ёмкость, выполняя новое выделение памяти в куче, когда она заполняется. Это также может привести к взаимным блокировкам, так как наш аллокатор также использует внутренний мьютекс. Другими проблемами являются то, что выделение памяти в куче может не удаться или занять значительное время, когда куча фрагментирована.
+Простая реализация такой очереди может быть основана на `VecDeque`, защищённом мьютексом. Однако использование мьютексов в обработчиках прерываний — не очень хорошая идея, так как это может легко привести к взаимным блокировкам (deadlock). Например, пользователь нажимает клавишу, но в тот же момент задача клавиатуру заблокировала очередь, обработчик прерываний пытается снова захватить блокировку и застревает навсегда. Ещё одна проблема с этим подходом в том, что `VecDeque` автоматически увеличивает свою ёмкость, выполняя новое выделение памяти в куче, когда она заполняется. Это также может привести к взаимным блокировкам, так как наш аллокатор также использует внутренний мьютекс. Более того, выделение памяти в куче может не получиться или занять значительное время, если куча фрагментирована.
-Чтобы предотвратить эти проблемы, нам нужна реализация очереди, которая не требует мьютексов или выделений памяти для своей операции `push`. Такие очереди могут быть реализованы с использованием неблокирующих [атомарных операций] для добавления и извлечения элементов. Таким образом, возможно создать операции `push` и `pop`, которые требуют только ссылки `&self` и могут использоваться без мьютекса. Чтобы избежать выделений памяти при `push`, очередь может быть основана на заранее выделенном буфере фиксированного размера. Хотя это делает очередь _ограниченной_ (_bounded_) (т.е. у неё есть максимальная длина), на практике часто возможно определить разумные верхние границы для длины очереди, так что это не представляет собой большой проблемы.
+Чтобы предотвратить эти проблемы, нам нужна реализация очереди, которая не требует мьютексов или выделений памяти для своей операции `push`. Такие очереди могут быть реализованы с использованием неблокирующих [атомарных операций][atiomic operations] для добавления и извлечения элементов. Таким образом, возможно создать операции `push` и `pop`, которые требуют только ссылки `&self` и могут использоваться без мьютекса. Чтобы избежать выделений памяти при `push`, очередь может быть основана на заранее выделенном буфере фиксированного размера. Хотя это делает очередь _ограниченной_ (_bounded_) (т.е. у неё есть максимальная длина), на практике часто возможно определить разумные верхние границы для длины очереди, так что это не представляет собой большой проблемы.
[atomic operations]: https://doc.rust-lang.org/core/sync/atomic/index.html
-##### The `crossbeam` Crate
+##### Крейт `crossbeam`
Реализовать такую очередь правильно и эффективно очень сложно, поэтому я рекомендую придерживаться существующих, хорошо протестированных реализаций. Один из популярных проектов на Rust, который реализует различные типы без мьютексов для конкурентного программирования — это [`crossbeam`]. Он предоставляет тип под названием [`ArrayQueue`], который именно то, что нам нужно в данном случае. И нам повезло: этот тип полностью совместим с `no_std` библиотеками, поддерживающими выделение памяти.
@@ -1072,7 +1072,7 @@ async fn example_task() {
Чтобы использовать этот тип, нам нужно добавить зависимость на библиотеку `crossbeam-queue`:
```toml
-# in Cargo.toml
+# Cargo.toml
[dependencies.crossbeam-queue]
version = "0.3.11"
@@ -1082,7 +1082,7 @@ features = ["alloc"]
По умолчанию библиотека зависит от стандартной библиотеки. Чтобы сделать её совместимой с `no_std`, нам нужно отключить её стандартные функции и вместо этого включить функцию `alloc`. (Заметьте, что мы также могли бы добавить зависимость на основную библиотеку `crossbeam`, которая повторно экспортирует библиотеку `crossbeam-queue`, но это привело бы к большему количеству зависимостей и более длительному времени компиляции.)
-##### Queue Implementation
+##### Реализация Очереди
Используя тип `ArrayQueue`, мы теперь можем создать глобальную очередь скан-кодов в новом модуле `task::keyboard`:
@@ -1116,11 +1116,11 @@ version = "0.2.0"
default-features = false
```
-Вместо примитива [`OnceCell`] мы также могли бы использовать макрос [`lazy_static`]. Однако тип `OnceCell` имеет то преимущество, что мы можем гарантировать, что инициализация не произойдёт в обработчике прерываний, тем самым предотвращая выполнение выделения памяти в куче в обработчике прерываний.
+Вместо примитива [`OnceCell`] мы также могли бы использовать макрос [`lazy_static`]. Однако тип `OnceCell` имеет то преимущество, что мы можем гарантировать, что инициализация не произойдёт в обработчике прерываний, тем самым предотвращая выполнение аллокации в куче в обработчике прерываний.
[`lazy_static`]: https://docs.rs/lazy_static/1.4.0/lazy_static/index.html
-#### Filling the Queue
+#### Наполнение очереди
Чтобы заполнить очередь скан-кодов, мы создаём новую функцию `add_scancode`, которую будем вызывать из обработчика прерываний:
@@ -1176,7 +1176,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(
Как и ожидалось, нажатия клавиш больше не выводятся на экран, когда мы запускаем наш проект с помощью `cargo run`. Вместо этого пишется предупреждение, что очередь не инициализирована при каждом нажатия клавиши.
-#### Scancode Stream
+#### Стрим для скан-кодов
Чтобы инициализировать `SCANCODE_QUEUE` и считывать скан-коды из очереди асинхронным способом, мы создаём новый тип `ScancodeStream`:
@@ -1198,11 +1198,11 @@ impl ScancodeStream {
Цель поля `_private` — предотвратить создание структуры из внешних модулей. Это делает функцию `new` единственным способом создать данный тип. В функции мы сначала пытаемся инициализировать статическую переменную `SCANCODE_QUEUE`. Если она уже инициализирована, мы вызываем панику, чтобы гарантировать, что можно создать только один экземпляр `ScancodeStream`.
-Чтобы сделать скан-коды доступными для асинхронных задач, далее нужно реализовать метод, подобный `poll`, который пытается извлечь следующий скан-код из очереди. Хотя это звучит так, будто мы должны реализовать трейт [`Future`] для нашего типа, здесь он не подходит. Проблема в том, что трейт `Future` абстрагируется только над одним асинхронным значением и ожидает, что метод `poll` не будет вызываться снова после того, как он вернёт `Poll::Ready`. Наша очередь скан-кодов, однако, содержит несколько асинхронных значений, поэтому нормально продолжать опрашивать её.
+Чтобы сделать скан-коды доступными для асинхронных задач, нужно реализовать метод, подобный `poll`, который пытается извлечь следующий скан-код из очереди. Хотя это звучит так, будто мы должны реализовать трейт [`Future`] для нашего типа, здесь он не подходит. Проблема в том, что трейт `Future` абстрагируется только над одним асинхронным значением и ожидает, что метод `poll` не будет вызываться снова после того, как он вернёт `Poll::Ready`. Наша очередь скан-кодов, однако, содержит несколько асинхронных значений, поэтому нормально продолжать опрашивать её.
-##### The `Stream` Trait
+##### Трейт `Stream`
-Поскольку типы, которые возвращают несколько асинхронных значений, являются распространёнными, библиотека [`futures`] предоставляет полезную абстракцию для таких типов: трейт [`Stream`]. Трейт определяется следующим образом:
+Поскольку типы, которые возвращают несколько асинхронных значений, являются распространёнными, библиотека [`futures`] предоставляет полезную абстракцию для таких типов: трейт [`Stream`]. Определение трейта:
[`Stream`]: https://rust-lang.github.io/async-book/05_streams/01_chapter.html
@@ -1220,11 +1220,11 @@ pub trait Stream {
- Ассоциированный тип называется `Item`, а не `Output`.
- Вместо метода `poll`, который возвращает `Poll`, трейт `Stream` определяет метод `poll_next`, который возвращает `Poll