Трейт LocalWake
Описание
⚡️ Это экспериментальное API, доступно только в ночных сборках Rust.
Аналогичный трейт Wake, но используемый для создания LocalWaker.
Этот API работает точно так же, как Wake, за исключением того, что он использует Rc вместо Arc, и результатом является LocalWaker вместо Waker.
Преимущества использования LocalWaker вместо Waker заключаются в том, что он позволяет локальному waker хранить данные, которые не реализуют Send и Sync. Дополнительно, это экономит вызовы Arc::clone, которые требуют атомарной синхронизации.
Синтаксис
#![allow(unused)] fn main() { pub trait LocalWake { // Обязательный метод fn wake(self: Rc<Self>); // Предоставляемый метод fn wake_by_ref(self: &Rc<Self>) { ... } } }
Обязательные методы
wake ⚡️
#![allow(unused)] fn main() { fn wake(self: Rc<Self>) }
Пробудить эту задачу.
Предоставляемые методы
wake_by_ref ⚡️
#![allow(unused)] fn main() { fn wake_by_ref(self: &Rc<Self>) }
Пробудить эту задачу без потребления локального waker.
Если исполнитель поддерживает более дешевый способ пробуждения без потребления waker, он должен переопределить этот метод. По умолчанию он клонирует Rc и вызывает wake на клоне.
Примеры
Это упрощенный пример функций spawn и block_on. Функция spawn используется для добавления новых задач в очередь выполнения, а функция block_on будет удалять их и опрашивать. Когда задача пробуждается, она помещает себя обратно в очередь выполнения для опроса исполнителем.
Примечание: Этот пример жертвует корректностью ради простоты. Реальный пример будет чередовать вызовы poll с вызовами io реактора для ожидания событий вместо циклического опроса.
#![allow(unused)] #![feature(local_waker)] fn main() { use std::task::{LocalWake, ContextBuilder, LocalWaker, Waker}; use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::cell::RefCell; use std::collections::VecDeque; thread_local! { // Очередь, содержащая все задачи, готовые к выполнению static RUN_QUEUE: RefCell<VecDeque<Rc<Task>>> = RefCell::default(); } type BoxedFuture = Pin<Box<dyn Future<Output = ()>>>; struct Task(RefCell<BoxedFuture>); impl LocalWake for Task { fn wake(self: Rc<Self>) { RUN_QUEUE.with_borrow_mut(|queue| { queue.push_back(self) }) } } fn spawn<F>(future: F) where F: Future<Output=()> + 'static + Send + Sync { let task = RefCell::new(Box::pin(future)); RUN_QUEUE.with_borrow_mut(|queue| { queue.push_back(Rc::new(Task(task))); }); } fn block_on<F>(future: F) where F: Future<Output=()> + 'static + Sync + Send { spawn(future); loop { let Some(task) = RUN_QUEUE.with_borrow_mut(|queue| queue.pop_front()) else { // выходим, так как в очереди больше нет задач return; }; // преобразуем Rc<Task> в `LocalWaker` let local_waker: LocalWaker = task.clone().into(); // Строим контекст с помощью `ContextBuilder` let mut cx = ContextBuilder::from_waker(Waker::noop()) .local_waker(&local_waker) .build(); // Опрашиваем задачу let _ = task.0 .borrow_mut() .as_mut() .poll(&mut cx); } } block_on(async { println!("hello world"); }); }
Совместимость с dyn
Этот трейт не совместим с dyn.
В старых версиях Rust совместимость с dyn называлась "object safety", поэтому этот трейт не является object safe.
Реализаторы
Автоматические реализации
Трейт LocalWake автоматически реализуется для:
- Типов, которые уже реализуют
LocalWake Rc<W>гдеW: LocalWake
Ручные реализации
Разработчики могут реализовать LocalWake для своих кастомных типов:
#![allow(unused)] #![feature(local_waker)] fn main() { use std::rc::Rc; use std::task::LocalWake; struct MyLocalWaker; impl LocalWake for MyLocalWaker { fn wake(self: Rc<Self>) { // Кастомная логика пробуждения } // Опционально переопределить wake_by_ref для оптимизации fn wake_by_ref(self: &Rc<Self>) { // Более эффективная реализация без клонирования } } }
Использование с исполнителями
Создание LocalWaker из типа LocalWake
#![allow(unused)] #![feature(local_waker)] fn main() { use std::rc::Rc; use std::task::LocalWaker; let my_local_waker = Rc::new(MyLocalWaker); let local_waker: LocalWaker = LocalWaker::from(my_local_waker); }
Интеграция с Context
#![allow(unused)] #![feature(local_waker)] fn main() { use std::task::{ContextBuilder, Waker}; let local_waker = Rc::new(MyLocalWaker).into(); let mut cx = ContextBuilder::from_waker(Waker::noop()) .local_waker(&local_waker) .build(); }
Преимущества перед Wake
- Отсутствие атомарных операций: использует
RcвместоArc - Поддержка !Send/!Sync типов: может хранить не-потокобезопасные данные
- Производительность: избегает накладных расходов атомарного подсчета ссылок
- Гибкость: подходит для однопоточных исполнителей
Ограничения
- ⚡️ Доступен только в ночных сборках Rust
- Не может быть использован в многопоточных сценариях
- Требует включения фичи
local_waker
Сравнение с Wake
| Характеристика | Wake | LocalWake |
|---|---|---|
| Тип указателя | Arc | Rc |
| Потокобезопасность | Send + Sync | !Send + !Sync |
| Атомарные операции | Да | Нет |
| Производительность | Ниже | Выше |
| Применение | Многопоточное | Однопоточное |
Рекомендации по использованию
- Используйте
LocalWakeдля однопоточных исполнителей - Переопределяйте
wake_by_refдля избежания клонированияRc - Комбинируйте с
ContextBuilderдля гибкого создания контекста - Используйте для оптимизации производительности в single-threaded средах