在最開始學習 Rust futures 的時候,executor 和 task 是兩個讓我比較困惑的概念,這兩個東西到底是啥,它們到底是如何使用的,我當時完全不清楚。等後來做完一些項目,才慢慢理解了。所以覺得有必要好好的記錄一下。
介紹
Executor 可以認爲是一個用來執行 future 的地方,我們可以在當前線程裏面執行 future,也可以將 future 扔到一個 thread pool 裏面去執行,也可以在 event loop 裏面(例如 tokio-core)裏面去執行。
而 Task 則可以認爲是一種正在或者將會被執行的 future。通常,我們會將多個 future 組合成一個大的工作單元,然後會在 executor 上面 spawn 一個對應的 task。Executor 會負責當通知到來的時候,去 poll future,直到 future 全被執行結束。
整個流程可以簡化爲:
- 當一個 future 不是 ready 的時候,我們使用
task::current()
函數得到一個 task handle,並 block 住當前的 future。 - 將 task handle 加入到一個感興趣的事件隊列裏面,如果相關事件觸發了,則通過
task.notify()
通知對應的 executor。 - Executor 繼續 poll future。
上面可能比較抽象,我們可以通過幾個例子更深刻的瞭解相關的機制。
Future wait
當我們創建了一個 future 之後,可以使用 wait 函數,block 住當前的線程,強制等到 future 被執行,然後纔會繼續進行後面的操作。
Future wait 函數的實現如下:
fn wait(self) -> result::Result<Self::Item, Self::Error>
where Self: Sized
{
::executor::spawn(self).wait_future()
}
可以看到,我們使用 executor::spawn
了一個 Spawn 對象,Spawn 對象表示的是一個 fused future 和 task, 這就意味着我們不能再將 future 跟其他的 future 去組合了,只能執行了。
在 wait_future
函數裏面,我們會 block 住當前的線程,直到 Spawn 內部的 future 執行完畢,代碼如下:
pub fn wait_future(&mut self) -> Result<F::Item, F::Error> {
let unpark = Arc::new(ThreadUnpark::new(thread::current()));
loop {
match self.poll_future_notify(&unpark, 0)? {
Async::NotReady => unpark.park(),
Async::Ready(e) => return Ok(e),
}
}
}
首先我們會創建一個 ThreadUnpark 的 Notify 對象,然後傳給 Spawn 的 poll_future_notify
去使用。當 future 變成 ready 的時候,我們會去調用 Notify 的 notify
函數去通知相關的 executor 繼續去 poll 這個 future。
在 ThreadUnpark 裏面,notify
實現如下:
impl Notify for ThreadUnpark {
fn notify(&self, _unpark_id: usize) {
self.ready.store(true, Ordering::SeqCst);
self.thread.unpark()
}
}
在 notify
函數裏面,我們直接會調用 thread 的 unpark
函數,用來喚醒當前被 block 的線程。
Spawn 的 poll_future_notify
會嘗試 poll 內部的 future,這個函數會接受一個 NotifyHandle 參數,後續任何的 task::current()
操作返回的 task handle 都會帶上這個 NotifyHandle,這樣我們通過 task.notify()
就能告訴 executor future 已經 ready 了。
如果 poll_future_notify
返回 NotReady,我們就需要靠 Notify 來通知了。在上面的例子中,返回 NotReady 之後,我們直接調用了 pack
函數,定義如下:
fn park(&self) {
if !self.ready.swap(false, Ordering::SeqCst) {
thread::park();
}
}
在 park
裏面,我們直接調用了 thread 的 park
函數,block 住了當前線程,這樣當 future 已經 ready 之後,我們會調用 thread 的 unpark
函數喚醒被 block 的線程。
gRPC
上面是一個簡單使用操作系統 thread 的 park/unpark
函數來處說明 Executor 和 Task 的例子,在 rust gRPC 裏面,我們爲了跟 gRPC 的 event loop 整合,也實現了相關的操作。
這裏先介紹一下 gRPC 的相關概念,在 gRPC 裏面,所有的事件都是通過 CompletionQueue ( 後面以 CQ 代替) 來驅動的,我們會不停的循環調用 CQ 的 next
函數,當有事件產生的時候,next
就會返回對應的事件,然後我們會通過這個事件裏面的 tag 找到對應的上下文繼續處理。
通常我們都是在 for 循環裏面調用的 next
函數,其它線程如果想跟 CQ 發送消息,就需要通過 gRPC 裏面的 alarm 機制,我們會先通過 grpc_alarm_create
創建一個 alarm,然後調用 grpc_alarm_cancel
就可以直接去通知到 CQ 了。當 next
返回對應的 alarm event 之後,我們就可以執行這個 alarm 相關的邏輯了。
當 CQ 線程調用到對應的 gRPC method 之後,我們可能需要在其他線程去處理相關的操作,這時候,就可以通過 executor::spawn
來生成一個 Spawn,代碼如下:
pub struct Executor<'a> {
cq: &'a CompletionQueue,
}
impl<'a> Executor<'a> {
pub fn spawn<F>(&self, f: F)
where
F: Future<Item = (), Error = ()> + Send + 'static,
{
let s = executor::spawn(Box::new(f) as BoxFuture<_, _>);
let notify = Arc::new(SpawnNotify::new(s, self.cq.clone()));
poll(notify, false)
}
}
SpawnNotify 對應的就是一個 Notify 對象,SpawnNotify 會創建一個 SpawnHandle,在對應的 notify
函數裏面,我們會調用 SpawnHandle 的 notify 函數,這個函數裏面就會創建一個 alarm 並通知 CQ。
pub fn notify(&mut self, tag: Box<CallTag>) {
self.alarm.take();
let mut alarm = Alarm::new(&self.cq, tag);
alarm.alarm();
// We need to keep the alarm until tag is resolved.
self.alarm = Some(alarm);
}
當 CQ 的 next
返回了對應的 alarm 事件之後,我們會調用到 SpawnNotify 的 resolve
函數:
pub fn resolve(self, success: bool) {
// it should always be canceled for now.
assert!(!success);
poll(Arc::new(self.clone()), true);
}
最後我們在關注下 poll
函數,無論是 Executor 的 spawn
還是SpawnNotify 的 resolve
裏面,我們最後都會使用。poll
會調用 Spawn 的 poll_future_notify
函數:
fn poll(notify: Arc<SpawnNotify>, woken: bool) {
let mut handle = notify.handle.lock();
......
match handle.f.as_mut().unwrap().poll_future_notify(¬ify, 0) {
Err(_) | Ok(Async::Ready(_)) => {
......
return;
}
_ => {}
}
}
poll_future_notify
如果返回 NotReady,這裏我們並不需要做特殊的處理,因爲 CQ 會不停的調用 next
,如果沒有任何事件產生,next
自動回 block 住當前的 CQ 的進程,如果 future 變成了 ready,我們就可以告訴 CQ,CQ 自然會在 next
裏面得到對應的事件,然後我們就能繼續去執行這個 future 了。
作者:siddontang
鏈接:https://www.jianshu.com/p/feafe6346929
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。