Pin
Async blocks and functions return types implementing the Future
trait. The type returned is the result of a compiler transformation which turns local variables into data stored inside the future.
Some of those variables can hold pointers to other local variables. Because of that, the future should never be moved to a different memory location, as it would invalidate those pointers.
To prevent moving the future type in memory, it can only be polled through a pinned pointer. Pin
is a wrapper around a reference that disallows all operations that would move the instance it points to into a different memory location.
use tokio::sync::{mpsc, oneshot}; use tokio::task::spawn; use tokio::time::{sleep, Duration}; // μμ νλͺ©. μ΄ κ²½μ° μ§μ λ μκ° λμ μ μ λͺ¨λμ΄κ³ // `respond_on` μ±λμ λ©μμ§λ‘ μλ΅ν©λλ€. #[derive(Debug)] struct Work { input: u32, respond_on: oneshot::Sender<u32>, } // νμμ μμ μ μμ λκΈ°νκ³ μ€ννλ workerμ λλ€. async fn worker(mut work_queue: mpsc::Receiver<Work>) { let mut iterations = 0; loop { tokio::select! { Some(work) = work_queue.recv() => { sleep(Duration::from_millis(10)).await; // μμ νλ μ²ν©λλ€. work.respond_on .send(work.input * 1000) .expect("μλ΅μ 보λ΄μ§ λͺ»νμ΅λλ€."); iterations += 1; } // TODO: 100λ°λ¦¬μ΄λ§λ€ λ°λ³΅ νμλ₯Ό λ³΄κ³ ν©λλ€. } } } // μμ μ μμ²νκ³ μμ μ΄ μλ£λκΈ°λ₯Ό κΈ°λ€λ¦¬λ μμ²μμ λλ€. async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 { let (tx, rx) = oneshot::channel(); work_queue .send(Work { input, respond_on: tx }) .await .expect("μμ νμμ μ μ‘νμ§ λͺ»νμ΅λλ€."); rx.await.expect("μλ΅ λκΈ° μ€ν¨") } #[tokio::main] async fn main() { let (tx, rx) = mpsc::channel(10); spawn(worker(rx)); for i in 0..100 { let resp = do_work(&tx, i).await; println!("λ°λ³΅ μμ κ²°κ³Ό {i}: {resp}"); } }
-
μμμ μκ°ν κ²μ μ‘ν°(actor) ν¨ν΄μ ν μλΌκ³ λ΄λ 무방ν©λλ€. μ‘ν°λ μΌλ°μ μΌλ‘ 루ν μμμ
select!
λ₯Ό νΈμΆν©λλ€. -
μ΄μ κ°μ λͺ κ°μ λ΄μ©μ μμ½ν κ²μ΄κΈ° λλ¬Έμ μ²μ²ν μ΄ν΄λ³΄μΈμ.
-
_ = sleep(Duration::from_millis(100)) => { println!(..) }
μselect!
μ μΆκ°ν΄ 보μΈμ. μ΄ μμ μ μ€νλμ§ μμ΅λλ€. μ κ·Έλ΄κΉμ? -
λμ , ν΄λΉ futureκ° ν¬ν¨λ
timeout_fut
λ₯Όloop
μΈλΆμ μΆκ°ν΄ 보μΈμ.#![allow(unused)] fn main() { let mut timeout_fut = sleep(Duration::from_millis(100)); loop { select! { .., _ = timeout_fut => { println!(..); }, } } }
-
μ¬μ ν μλνμ§ μμ΅λλ€. μ»΄νμΌλ¬ μ€λ₯λ₯Ό λ°λΌ
select!
μtimeout_fut
μ&mut
λ₯Ό μΆκ°νμ¬ Move μλ©ν± κ΄λ ¨ν λ¬Έμ λ₯Ό ν΄κ²°νκ³Box::pin
μ μ¬μ©νμΈμ.#![allow(unused)] fn main() { let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100))); loop { select! { .., _ = &mut timeout_fut => { println!(..); }, } } }
-
μ΄λ μ»΄νμΌμ λμ§λ§ νμ μμμ΄ λλ©΄ λ§€λ² λ°λ³΅ν λ λ§λ€
Poll::Ready
κ° λ©λλ€(μ΅ν©λ futureκ° λμμ΄ λ μ μμ). νμ μμ λ λλ§λ€timeout_fut
λ₯Ό 리μ νλλ‘ μμ νμΈμ.
-
-
Boxλ νμ ν λΉν©λλ€. κ²½μ°μ λ°λΌ
std::pin::pin!
(μ΅κ·ΌμμΌ μμ νλμμΌλ©° μ΄μ μ½λλtokio::pin!
μ μ¬μ©νλ κ²½μ°κ° λ§μ)λ μ¬μ©ν μ μμ§λ§ μ΄λ μ¬ν λΉλ futureμ μ¬μ©νκΈ°κ° μ΄λ ΅μ΅λλ€. -
λ λ€λ₯Έ λ°©λ²μ
pin
μ μμ μ¬μ©νμ§ μκ³ 100msλ§λ€oneshot
μ±λμ μ μ‘ν λ€λ₯Έ μμ μ μμ±νλ κ²μ λλ€. -
Data that contains pointers to itself is called self-referential. Normally, the Rust borrow checker would prevent self-referential data from being moved, as the references cannot outlive the data they point to. However, the code transformation for async blocks and functions is not verified by the borrow checker.
-
Pin
is a wrapper around a reference. An object cannot be moved from its place using a pinned pointer. However, it can still be moved through an unpinned pointer. -
The
poll
method of theFuture
trait usesPin<&mut Self>
instead of&mut Self
to refer to the instance. Thatβs why it can only be called on a pinned pointer.