今天我們繼續高并發的話題,傳統的云計算技術,本質上都是基于虛擬機的,云平臺可以將一些性能強勁的物理服務器,拆分成若干個虛擬機,提供給用戶使用,但在互聯網發展到今天,虛擬機還是太重了,即使是飛天集群,新增部署虛擬機的時間也是以分鐘來計的,但是對于互聯網用戶來講20秒的等等就是就會千萬50%以上的用戶流失,不能忍受的煎熬,因此無GC更不依賴JVM的Rust,無論在冷啟速度還是在記憶體消耗上都比JAVA和GO更具優勢,而且相比C語言Rust的生產效率也更高,很多儲如從函式式語言借鑒而來的Future機制都非常先進,根據官方的測驗結果,在性能方面Rust的網路編程框架比JAVA和GO要好得多

但是我意外的看到像Rust中Tokio這樣優秀的高并發網路編程框架在中文技術社區卻沒有個完整的教程,但是在周末雞娃的時候,我意外發現小朋友對于Future、async/await機制理解起來卻很容易 ,因此筆者決定將這段時間與小孩一起探索Tokio的心得向大家分享一下,做為基礎入門的學習材料,
初識Tokio
Tokio是基于Rust開發的異地網路編程框架,用于執行異步代碼的多執行緒運行時,通過Future、async/await等機制,開發者可以讓代碼產生極高生產力的同時保持程式的性能基本與C語言一致,基于Tokio的開發在撰寫異步代碼時,開發者不能使用Rust標準庫提供的阻塞api,而必須使用由Tokio提供,鏡像了Rust標準庫的API,我們先來看一個Tokio的Helloworld程式
1.首先創建專案
cargo new my-tokio
命令創建一個my-tokio的專案
- 修改Cargo.toml
vi Cargo.toml
在依賴處添加以下內容
[dependencies]
tokio = { version = "1", features = ["full"] }
- 修改源代碼
vi src/main.rs
并將代碼替換為以下內容
async fn say_word() {
println!("my tokio");
}
#[tokio::main]
async fn main() {
let op = say_word();
println!("hello");
op.await;
}
- 編譯并執行
cargo build
cargo run
結果如下:
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/my-tokio`
hello
my tokio
這里我們先解釋一下async和await的用法,我們看到async fn say_word()中,say_word()函式是被async關鍵詞修飾的,那么也就是說這個函式在被呼叫時 let op = say_word();
,以上代碼是被立即回傳而沒有被執行的,而這時op實際是一個Future,也就是一個現在為空,在未來才會產生的值(有關Future的機制我們接下來解釋),而在呼叫op.await的時其實是在等到這個async異步操作執行完畢才回傳,是一個阻塞操作,因此最終輸出會是先列印hello,然后再列印my tokio
?
程式 程式員如何理解更像自然語言的Future
在以下這段代碼中,網路連接socket、請求發送request、回應接收response三個物件全部都是future型別的,也就是在代碼執行之后不會被執行也沒有值僅有占位的意義,當未來執行后才會有值回傳,and_then方法其實是在future物件執行成功后才會被呼叫的方法,比如read_to_end這行代碼就是在request物件執行成功后,呼叫read_to_end方法對讀取結果,
use futures::Future;
use tokio_core::reactor::Core;
use tokio_core::net::TcpStream;fn main() {
let mut core = Core::new().unwrap();
let addr = "127.0.0.1:8080".to_socket_addrs().unwrap().next().unwrap();
let socket = TcpStream::connect(&addr, &core.handle());
let request = socket.and_then(|socket|{
tokio_core::io::write_all(socket, "Hello World".as_bytes())
});
let response = request.and_then(|(socket, _)| {
tokio_core::io::read_to_end(socket, Vec::new())
});
let (_, data) = core.run(response).unwrap();
println!("{}", String::from_utf8_lossy(&data));
}
而想象一下如果是傳統編程所采用的方式,需要在網路連接完成后呼叫請求發送的回呼函式,然后再請求發送的回應處理方法中再注冊接收請求的回呼函式,復雜不說還容易出錯,
上面的代碼就是建立Tcp連接,發送資料,最后讀取回傳,每個Future都是通過and_then建立關系,而future機制精髓之處在于,整個程序是通過core.run(response).unwrap();這行代碼運行起來的,也就是說在Future的幫助下,程式員只需要關心最終的結果就可以了,整個鏈條通過poll機制串聯,從poll機制來看,這幾個模塊的傳遞機制如下:

從建立網路連接開始的呼叫鏈交給計算機去幫你完成,不但省去了回呼所帶來的復雜性,最終的效率反而還會更高,
poll模制到底是什么意思?
筆者看到不少博主在介紹Rust的Future等異步編程框架時都提到了Rust的Future采用poll模式,不過到底什么是poll模式卻大多語焉不詳,其實poll做的本質作業就是監測鏈條上前續Future的執行狀態,

以上述情況為例,poll的方向是由response到request最后是socket,但是state和data的回傳方向是完全返過來的,也就是說response通過poll來獲取request的state,而request也同樣通過poll來獲取socket的state,
筆者還是這樣的觀點,程式員群體之所以覺得future機制難以理解,其關鍵在于思維模式被計算機的各種回呼機制給束縛住了,而忘記了最簡單直接的方式,在解決這個問題之前我們先來問一個問題,假如讓我們自己設計一個類似于tokio這樣的異步Future管理器,應該如何入手?
最直接也是最容易想到的方案就是事件回圈,定期遍歷整個事件佇列,把狀態是ready的事件通知給對應的處理程式,這也是我們常說的select方案;另外一種做法是在事件poll管理器中直接拿到處理程式的句柄,不再遍歷整個事件佇列,而是直接在中斷處理回應中把通知發給對應處理行程,比如上述例子中實際是按照poll的鏈條傳遞的處理行程句柄的,這就是Poll模式,而基于poll設計的如tokio框架進行應用開發時,程式員根本不必關心整個訊息傳遞,只需要用and_then、spawn等方法建立鏈條并讓系統作業起來就可以了,
而epoll(多路復用)是基于poll的另一種高并發機制,這種機制可以監視多個描述符,一旦某個描述符狀態變為就緒,能夠通知對應的handler進行后續操作,筆者在前文《這位創造了Github冠軍專案的老男人,堪稱10倍程式員本尊》中曾經介紹過Tdengine的定時器,其中就有這種多路復用的思想,由于作業系統timer的處理程式還不支持epoll的多路復用,因此每注冊一個timer就必須要啟動一個執行緒進行處理,資源浪費嚴重,因此Tdengine自己實作了一個多路復用的timer,可以做到一個執行緒同時處理多個timer,這些細節上的精巧設計也是Tdengine封神的原因之一,
后記
寫到這突然發現tokio框架的介紹一篇文章根本就不可能完成,那么本文權當一個基礎介紹,為入門tokio做準備,如果后面讀者們再有強烈需求,我們再繼續聊這個話題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299783.html
標籤:其他
下一篇:暴力破解
