Rust的併發執行緒 - ibraheem

banq發表於2022-01-13

下面是一個簡單的 Web 伺服器:

use std::net::{TcpListener, TcpStream};
use std::io::{BufRead, BufReader, Write};

fn main() {
    let listener = TcpListener::bind("localhost:3000").expect("failed to create TCP listener");

    for connection in listener.incoming() {
        let stream = connection.expect("client connection failed");
        handle_connection(stream)
    }
}

fn handle_connection(mut stream: TcpStream) {
    let mut reader = BufReader::new(&mut stream);

    let mut request = Vec::new();
    reader
        .read_until(b'\n', &mut request)
        .expect("failed to read from stream");

    let request = String::from_utf8(request).expect("malformed request line");
    print!("HTTP request line: {}", request);

    let response = concat!(
        "HTTP/1.1 200 OK\r\n",
        "Content-Length: 12\n",
        "Connection: close\r\n\r\n",
        "Hello world!"
    );

    stream
        .write(response.as_bytes())
        .expect("failed to write to stream");
    stream.flush().expect("failed to flush stream");
}


但是這個Web一次只能處理一個請求,我們需要能夠同時(同時)處理多個請求。為此,我們將使用多執行緒。
每個程式都從一個執行緒開始,即主執行緒。我們可以用std::thread::spawn. 因為我們希望能夠同時處理多個請求,所以我們將每個請求處理程式派生到一個新執行緒上:

fn main() {
    let listener = TcpListener::bind("localhost:3000").expect("failed to create TCP listener");

    for connection in listener.incoming() {
        let stream = connection.expect("client connection failed");
        std::thread::spawn(|| handle_connection(stream));
    }
}

spawn函式在建立一個新執行緒後立即返回,而不是在主執行緒上執行。
透過這個簡單的更改,我們現在可以同時處理許多請求。當一個執行緒正在等待讀取、寫入或執行任何其他操作時,其他準備就緒的執行緒將被允許執行。而且由於作業系統排程程式是搶佔式的,我們的伺服器永遠不會因需要很長時間的請求而被阻塞。
這個解決方案效果很好,因為我們的伺服器是I/O bound。在我們要求從連線中讀取資料和取回資料之間,我們不一定要做任何事情。我們只是在等待,受到網路資料輸入/輸出速度的限制。一個CPU限制,另一方面應用處理器的限制,因為它不斷地做工作。讓它執行得更快的唯一方法是新增更多的 CPU 核心或將工作分配到多臺機器上。
 
讓我們看看伺服器現在真的快了多少!
單執行緒版本每秒能夠處理 10 個請求,平均需要 10秒來響應。另一方面,多執行緒伺服器每秒處理近 5000 個請求,延遲僅超過 100 毫秒。鑑於我們在處理程式中讓執行緒sleep了 100 毫秒,這非常好:)
 

相關文章