譯自Rust futures: an uneducated, short and hopefully not boring tutorial - Part 3 - The reactor 本文時間:2018-12-03,譯者: motecshine, 簡介:motecshine
歡迎向Rust中文社區投稿,投稿地址 ,好文將在以下地方直接展示
Intro
在這篇文章中我們將會討論和闡釋Reactor
是如何工作的.在上篇文章中我們,我們頻繁的使用Reactor
來執行我們的Future
,但是並沒有闡述它是如何工作的。現在是時候闡明它了。
Reactor? Loop?
如果用一句話來描述Reactor
,那應該是:
Reactor
是一個環(Loop
)
舉個栗子:
你決定通過Email邀請你喜歡的女孩或者男孩(emmm, 這個栗子聽起來很老套), 你懷着忐忑的心將這份郵件發送出去,心裏焦急着等待着, 不停的一遍又一遍的檢查你的郵箱是否有新的回覆. 直到收到回覆。
Rust's Reactor
就是這樣, 你給他一個future
, 他會不斷的檢查,直到這個future
完成(或者返回錯誤). Reactor
通過調用程序員實現的Poll
函數,來檢查Future
是否已完成。你所要做的就是實現future poll
並且返回Poll<T, E>
結構。但是 Reactor
也不會無休止的對你的future function
輪詢。
A future from scratch
爲了讓我們能更容易理解Reactor
知識,我們還是從零開始實現一個Future
. 換句話說就是,我們將動手實現Future Trait
.
#[derive(Debug)]
struct WaitForIt {
message: String,
until: DateTime<Utc>,
polls: u64,
}
我們的結構體字段也很簡單:
- message: 自定義字符串消息體
- polls: 輪循次數
- util: 等待時間
我們還會實現 WaitFotIt
結構體的new
方法.這個方法作用是初始化WaitForIt
impl WaitForIt {
pub fn new(message: String, delay: Duration) -> WaitForIt {
WaitForIt {
polls: 0,
message: message,
until: Utc::now() + delay,
}
}
}
impl Future for WaitForIt {
type Item = String;
type Error = Box<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let now = Utc::now();
if self.until < now {
Ok(Async::Ready(
format!("{} after {} polls!", self.message, self.polls),
))
} else {
self.polls += 1;
println!("not ready yet --> {:?}", self);
Ok(Async::NotReady)
}
}
}
讓我們逐步解釋
type Item = String;
type Error = Box<Error>;
上面兩行在RUST
裏被叫做associated types
, 意思就是Future
在將來完成時返回的值(或者錯誤).
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {}
定義輪詢的方法。Self::Item, Self::Error
是我們定義的associated types
站位符。在我們的例子中,該方法如下:
fn poll(&mut self) - > Poll <String,Box <Error >>
現在看看我們的邏輯代碼:
let now = Utc::now();
if self.until < now {
// 告訴reactor `Future` 已經完成了!
} else {
// 告訴 reactor `Future` 還沒準備好,過會兒再來。
}
在Rust
裏我們該怎樣告訴Reactor
某個Future
已經完成了?很簡單使用枚舉
Ok(Async::NotReady(.......)) // 還沒完成
Ok(Async::Ready(......)) // 完成了
讓我們來實現上述的方法:
impl Future for WaitForIt {
type Item = String;
type Error = Box<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let now = Utc::now();
if self.until < now {
Ok(Async::Ready(
format!("{} after {} polls!", self.message, self.polls),
))
} else {
self.polls += 1;
println!("not ready yet --> {:?}", self);
Ok(Async::NotReady)
}
}
}
爲了讓這段代碼運行起來我們還需要:
extern crate chrono;
extern crate futures;
extern crate tokio_core;
use futures::done;
use futures::prelude::*;
use futures::future::{err, ok};
use tokio_core::reactor::Core;
use std::error::Error;
use futures::prelude::*;
use futures::*;
use chrono::prelude::*;
use chrono::*;
fn main() {
let mut reactor = Core::new().unwrap();
let wfi_1 = WaitForIt::new("I'm done:".to_owned(), Duration::seconds(1));
println!("wfi_1 == {:?}", wfi_1);
let ret = reactor.run(wfi_1).unwrap();
println!("ret == {:?}", ret);
}
運行!! 等待一秒我們將會看到結果:
Running `target/debug/tst_fut_create`
wfi_1 == WaitForIt { message: "I\'m done:", until: 2017-11-07T16:07:06.382232234Z, polls: 0 }
not ready yet --> WaitForIt { message: "I\'m done:", until: 2017-11-07T16:07:06.382232234Z, polls: 1 }
emmm~, 只運行一次就被卡住了, 但是沒有額外的消耗CPU
.但是爲什麼會這樣?
如果不明確告訴
Reactor
,Reactor
是不會再次輪詢停放(park)給它的Future
.
(- 譯註: Park: 翻譯成停放其實也挺好的,就像車場的停車位一樣.)
在我們的例子裏, Reactor
會立即執行我們停放的Future
方法, 當我們返回Async::NotReady
, 它就會認爲當前停放的Future
還未完成。如果我們不主動去解除停放,Reactor
永遠也不會再次調用。
空閒中的Reactor
是不會消耗CPU的。這樣看起來Reactor
效率還是很高的。
在我們的電子郵件示例中,我們可以避免手動檢查郵件並等待通知。 所以我們可以在此期間自由玩Doom。(emm~看來作者很喜歡這款遊戲).
另一個更有意義的示例可能是從網絡接收數據。 我們可以阻止我們的線程等待網絡數據包,或者我們等待時可以做其他事情。 您可能想知道爲什麼這種方法比使用OS線程更好?
Unparking
我們該如何糾正我們例子?我們需要以某種方式取消我們的Future
。 理想情況下,我們應該有一些外部事件來取消我們的Future
(例如按鍵或網絡數據包),但是對於我們的示例,我們將使用這個簡單的行手動取消停放
futures::task::current().notify();
像這樣:
impl Future for WaitForIt {
type Item = String;
type Error = Box<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let now = Utc::now();
if self.until < now {
Ok(Async::Ready(
format!("{} after {} polls!", self.message, self.polls),
))
} else {
self.polls += 1;
println!("not ready yet --> {:?}", self);
futures::task::current().notify();
Ok(Async::NotReady)
}
}
}
現在代碼完成了。 請注意,在我的情況下,該函數已被調用超過50k次, CPU佔用也很高!
這是嚴重的浪費,也清楚地說明你爲什麼需要在某個合理的時間點去Unpark Future
.( That's a waste of resources and clearly demonstrates why you should unpark your future only when something happened. )
另請注意循環如何僅消耗單個線程。 這是設計和效率的來源之一。 當然,如果需要,您可以使用更多線程。
Joining
Reactor
可以同時運行多個Future
,這也是他爲什麼如此有https://github.com/rustlang-cn/forum/edit/master/translate/futures/深入淺出Rust-Future-Part-3.md效率的原因. 那麼我們該如何充分利用單線程: 當一個Future
被停放的時候, 另一個可以繼續工作。
對於這個例子,我們將重用我們的WaitForIt結構。 我們只是同時調用兩次。 我們開始創建兩個Future
的實例:
let wfi_1 = WaitForIt::new("I'm done:".to_owned(), Duration::seconds(1));
println!("wfi_1 == {:?}", wfi_1);
let wfi_2 = WaitForIt::new("I'm done too:".to_owned(), Duration::seconds(1));
println!("wfi_2 == {:?}", wfi_2);
現在我們來調用futures::future::join_all
, 他需要一個vec![]
迭代器, 並且返回枚舉過的Future
let v = vec![wfi_1, wfi_2];
let sel = join_all(v);
我們重新實現的代碼像這樣:
fn main() {
let mut reactor = Core::new().unwrap();
let wfi_1 = WaitForIt::new("I'm done:".to_owned(), Duration::seconds(1));
println!("wfi_1 == {:?}", wfi_1);
let wfi_2 = WaitForIt::new("I'm done too:".to_owned(), Duration::seconds(1));
println!("wfi_2 == {:?}", wfi_2);
let v = vec![wfi_1, wfi_2];
let sel = join_all(v);
let ret = reactor.run(sel).unwrap();
println!("ret == {:?}", ret);
}
這裏的關鍵點是兩個請求是交錯的:第一個Future
被調用,然後是第二個,然後是第一個,依此類推,直到兩個完成。 如上圖所示,第一個Future
在第二個之前完成。 第二個在完成之前被調用兩次。
Select
Future
的特性還有很多功能。 這裏值得探討的另一件事是select函數。 select函數運行兩個(或者在select_all的情況下更多)Future
,並返回第一個完成。 這對於實現超時很有用。 我們的例子可以簡單:
fn main() {
let mut reactor = Core::new().unwrap();
let wfi_1 = WaitForIt::new("I'm done:".to_owned(), Duration::seconds(1));
println!("wfi_1 == {:?}", wfi_1);
let wfi_2 = WaitForIt::new("I'm done too:".to_owned(), Duration::seconds(2));
println!("wfi_2 == {:?}", wfi_2);
let v = vec![wfi_1, wfi_2];
let sel = select_all(v);
let ret = reactor.run(sel).unwrap();
println!("ret == {:?}", ret);
}
Closing remarks
下篇將會創建一個更Real
的Future
.
可運行的代碼
extern crate chrono;
extern crate futures;
extern crate tokio_core;
use futures::done;
use futures::prelude::*;
use futures::future::{err, ok};
use tokio_core::reactor::Core;
use std::error::Error;
use futures::prelude::*;
use futures::*;
use chrono::prelude::*;
use chrono::*;
use futures::future::join_all;
#[derive(Debug)]
struct WaitForIt {
message: String,
until: DateTime<Utc>,
polls: u64,
}
impl WaitForIt {
pub fn new(message: String, delay: Duration) -> WaitForIt {
WaitForIt {
polls: 0,
message: message,
until: Utc::now() + delay,
}
}
}
iml Future for WaitForIt {
type Item = String;
type Error = Box<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let now = Utc::now();
if self.until < now {
Ok(Async::Ready(
format!("{} after {} polls!", self.message, self.polls),
))
} else {
self.polls += 1;
println!("not ready yet --> {:?}", self);
futures::task::current().notify();
Ok(Async::NotReady)
}
}
}
fn main() {
let mut reactor = Core::new().unwrap();
let wfi_1 = WaitForIt::new("I'm done:".to_owned(), Duration::seconds(1));
println!("wfi_1 == {:?}", wfi_1);
let wfi_2 = WaitForIt::new("I'm done too:".to_owned(), Duration::seconds(1));
println!("wfi_2 == {:?}", wfi_2);
let v = vec![wfi_1, wfi_2];
let sel = join_all(v);
let ret = reactor.run(sel).unwrap();
println!("ret == {:?}", ret);
}