Rust的并发线程 - ibraheem


下面是一个简单的 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 毫秒,这非常好:)