001 Rust 非同步程式設計,Why Async

linghuyichong發表於2020-05-24

我們之前已經學習過Rust程式設計基礎相關的一些知識,下面進入到Rust非同步程式設計的學習,本節主要介紹為什麼需要Rust非同步程式設計。

假定有一個客戶端,需要從2個不同的網站進行下載任務,我們用Rust程式模擬下載過程,並說明為什麼需要Rust非同步程式設計。

Server1

use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::thread;
use std::time;

fn handle_client(mut stream: TcpStream, wait_time: u64) -> std::io::Result<()> {
    let mut buf = [0; 512];
    loop {
        let bytes_read = stream.read(&mut buf)?;
        if bytes_read == 0 {
            return Ok(());
        }

        thread::sleep(time::Duration::from_secs(wait_time));
        stream.write(&buf[..bytes_read])?;
        stream.write(&("\n".as_bytes()))?;
    }    
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080")?;

    for stream in listener.incoming() {
        handle_client(stream?, 20)?;
    }

    Ok(())
}

server2

use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::thread;
use std::time;

fn handle_client(mut stream: TcpStream, wait_time: u64) -> std::io::Result<()> {
    let mut buf = [0; 512];
    loop {
        let bytes_read = stream.read(&mut buf)?;
        if bytes_read == 0 {
            return Ok(());
        }

        thread::sleep(time::Duration::from_secs(wait_time));
        stream.write(&buf[..bytes_read])?;
        stream.write(&("\n".as_bytes()))?;
    }    
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8081")?;

    for stream in listener.incoming() {
        handle_client(stream?, 10)?;
    }

    Ok(())
}

client

迭代一

use std::net::TcpStream;
use std::io::{ prelude::*, BufReader, Write };
use std::str;

fn use_server(server: &str, port: u16, content: &str) -> std::io::Result<()> {
    let mut stream = TcpStream::connect((server, port))?;
    let _ = stream.write(content.as_bytes())?;

    let mut reader = BufReader::new(&stream);
    let mut buffer: Vec<u8> = Vec::new();
    reader.read_until(b'\n', &mut buffer)?;

    println!("recv from server: {} ", str::from_utf8(&buffer).unwrap());
    Ok(())
}

//迭代一
fn main() -> std::io::Result<()> {
    use_server("127.0.0.1", 8080, "use server 127.0.0.1:8080")?;
    use_server("127.0.0.1", 8081, "use server 127.0.0.1:8081")?;

    Ok(())
}

程式存在的問題:

必須從第一個網站下載完成後才能從第二個網站進行下載,不符合實際下載情況。

迭代二

針對迭代一中的問題,解決辦法如下:

使用多執行緒實現,每個下載任務都起一個執行緒。

程式碼如下:

use std::net::TcpStream;
use std::io::{ prelude::*, BufReader, Write };
use std::str;
use std::thread;

fn use_server(server: &str, port: u16, content: &str) -> std::io::Result<()> {
    let mut stream = TcpStream::connect((server, port))?;
    let _ = stream.write(content.as_bytes())?;

    let mut reader = BufReader::new(&stream);
    let mut buffer: Vec<u8> = Vec::new();
    reader.read_until(b'\n', &mut buffer)?;

    println!("recv from server: {} ", str::from_utf8(&buffer).unwrap());
    Ok(())
}

迭代二
fn main() -> std::io::Result<()> {
    let mut handles: Vec<thread::JoinHandle<()>> = Vec::new(); 
    let handle = thread::spawn(move || {
        use_server("127.0.0.1", 8080, "use server 127.0.0.1:8080").unwrap();
    });
    handles.push(handle);

    let handle = thread::spawn(move || {
        use_server("127.0.0.1", 8081, "use server 127.0.0.1:8081").unwrap();
    });
    handles.push(handle);

    for handle in handles {
        handle.join().unwrap();
    }
    Ok(())
 }

程式存在問題:

1、如果有幾千幾萬個任務,每個任務都起一個執行緒,怎麼辦?
2、如果使用執行緒池解決,但是我們每個下載任務其實都是發起請求後在等待server的響應,效率還是不夠高。那麼有沒有什麼辦法解決這些問題?

迭代三

針對迭代二中的問題,可以考慮用非同步程式設計來解決,即不需要起多個執行緒,每個任務就緒後再執行,修改過程如下:

  • 配置檔案Cargo.toml

    [dependencies]
    futures = "0.3.4"
  • 原始碼:

    use std::net::TcpStream;
    use std::io::{ prelude::*, BufReader, Write };
    use std::str;
    use futures::join;
    use futures::executor;
    fn use_server(server: &str, port: u16, content: &str) -> std::io::Result<()> {
      let mut stream = TcpStream::connect((server, port))?;
      let _ = stream.write(content.as_bytes())?;
    
      let mut reader = BufReader::new(&stream);
      let mut buffer: Vec<u8> = Vec::new();
      reader.read_until(b'\n', &mut buffer)?;
    
      println!("recv from server: {} ", str::from_utf8(&buffer).unwrap());
      Ok(())
    }
    //迭代三
    async fn async_use_server(server: &str, port: u16, content: &str) {
      use_server(server, port, content).unwrap();
    }
    async fn use_all_server() {
      let f1 = async_use_server("127.0.0.1", 8080, "use server 127.0.0.1:8080");
      let f2 = async_use_server("127.0.0.1", 8081, "use server 127.0.0.1:8081");
      join!(f1, f2);
    }
    fn main() -> std::io::Result<()> {
      let f = use_all_server();
      executor::block_on(f);
      Ok(())
    }

透過Rust非同步程式設計,我們期望能夠達到不使用多執行緒達到迭代二中多執行緒一樣的效果。

執行程式,發現並沒有符合我們的預期,執行結果仍然是先下載第一個任務,再下載第二個任務。為什麼會這樣呢

這裡留個懸念,本節主要說明為什麼需要非同步程式設計,至於程式為什麼不及預期,我們會在以後的章節中給大家解惑。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
令狐一衝

相關文章