用Rust实现一个多线程的web server

在本文之前,我们用Rust实现一个单线程的web server的例子,但是单线程的web server不够高效,所以本篇文章就来实现一个多线程的例子。

单线程web server存在的问题

请求只能串行处理,也就是说当第一个连结处理完之前不会处理第二个连结。考虑如下例子:

use std::net::{TcpListener, TcpStream};use std::io::{Read, Write};use std::fs;use std::{thread, time};fn handle_client(mut stream: TcpStream) {    let mut buffer = [0; 512];    stream.read(&mut buffer).unwrap();    let get = b"GET / HTTP/1.1\r\n";    let (status_line, filename) = if buffer.starts_with(get) {        ("HTTP/1.1 200 OK\r\n\r\n", "main.html")    } else {        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")    };    let contents = fs::read_to_string(filename).unwrap();    let response = format!("{}{}", status_line, contents);    stream.write(response.as_bytes()).unwrap();    stream.flush().unwrap();        let ten_millis = time::Duration::from_millis(10000);     thread::sleep(ten_millis);              //睡眠一段时间,模拟处理时间很长}fn main() -> std::io::Result<()> {    let listener = TcpListener::bind("127.0.0.1:8080")? ;    for stream in listener.incoming() {        handle_client(stream?);    }    Ok(())}

在浏览器中打开两个窗口,分别输入127.0.0.1:8080,会发现在第一个处理完之前,第二个不会响应。

使用多线程来解决问题

  • 解决方式

修改main函数代码:

fn main() -> std::io::Result<()> {    let listener = TcpListener::bind("127.0.0.1:8080")?;    let mut thread_vec: Vec> = Vec::new();    for stream in listener.incoming() {        // handle_client(stream?);        let stream = stream.unwrap();        let handle = thread::spawn(|| {            handle_client(stream);        });        thread_vec.push(handle);    }    for handle in thread_vec {        handle.join().unwrap();    }        Ok(())}

从浏览器打开两个标签,进行测试,可以发现第一个没有处理完之前,第二个请求已经开始处理。

  • 存在问题

当存在海量请求时,系统也会跟着创建海量的线程,最终造成系统崩溃。

使用线程池来解决问题

  • 线程池
用Rust实现一个多线程的web server
  • 知识点

多线程、管道。

从主线程将任务发送到管道,工作线程等待在管道的接收端,当收到任务时,进行处理。

线程池方式实现

1、初步设计

  • 定义ThreadPool结构
use std::thread;pub struct ThreadPool {             thread: Vec>,}
  • 定义ThreadPool的方法
impl ThreadPool {   pub fn new(size: usize) -> ThreadPool {     //--snip--  }       pub fn execute()    //pub fn execute(&self, f: F)    //    where    //        F: FnOnce() + Send + 'static    {      //--snip--    }}
  • 下面我们考虑new函数,可能的实现是这样
pub fn new(size: usize) -> ThreadPool { assert!(size > 0);  let mut threads = Vec::with_capacity(size); for _ in 0..size {      //创建线程:     //问题来了,创建线程的时候需要传入闭包,也就是具体做的动作,     //可是这个时候我们还没有具体的任务,怎么办? }           ThreadPool {        threads }}
  • execute函数
//设计execute的函数,可以参考thread::spawnpub fn execute(&self, f: F)    where        F: FnOnce() + Send + 'static{}

初步设计的问题总结:

主要是在创建线程池的new函数中,需要传入具体的任务,可是此时还没有具体的任务,如何解决?

2、解决线程创建的问题

  • 重新定义ThreadPool结构体
pub struct ThreadPool {    workers: Vec,}
  • ThreadPool的new方法
pub fn new(size: usize) -> ThreadPool {    assert!(size > 0);    let mut workers = Vec::with_capacity(size);    for id in 0..size {        workers.push(Worker::new(id));    }    ThreadPool {        workers    }}
  • 在worker中创建线程
struct Worker {    id: usize,    thread: thread::JoinHandle<()>,}impl Worker {    fn new(id: usize) -> Worker {        let thread = thread::spawn(|| {});        Worker {            id,            thread,        }    }}

3、发送任务

  • 进一步将ThreadPool结构设计为
use std::sync::mpsc;pub struct ThreadPool {    workers: Vec,    sender: mpsc::Sender,}struct Job;
  • 完善new方法
impl ThreadPool {    // --snip--    pub fn new(size: usize) -> ThreadPool {        assert!(size > 0);        let (sender, receiver) = mpsc::channel();//add        let mut workers = Vec::with_capacity(size);        for id in 0..size {            //workers.push(Worker::new(id));            workers.push(Worker::new(id, receiver));        }        ThreadPool {            workers,            sender,//add        }    }    // --snip--}//--snip--impl Worker {    fn new(id: usize, receiver: mpsc::Receiver) -> Worker {        let thread = thread::spawn(|| {            receiver;        });        Worker {            id,            thread,        }    }}

此段代码错误,因为receiver要在线程间传递,但是是非线程安全的。因此应该使用Arc>。重新撰写new方法如下:

use std::sync::Arc;use std::sync::Mutex;// --snip--impl ThreadPool {    // --snip--    pub fn new(size: usize) -> ThreadPool {        assert!(size > 0);        let (sender, receiver) = mpsc::channel();        let receiver = Arc::new(Mutex::new(receiver));//add        let mut workers = Vec::with_capacity(size);        for id in 0..size {            workers.push(Worker::new(id, Arc::clone(&receiver)));        }        ThreadPool {            workers,            sender,        }    }    // --snip--}impl Worker {    fn new(id: usize, receiver: Arc>>) -> Worker {        let thread = thread::spawn(move || {            loop {                let job = receiver.lock().unwrap().recv().unwrap();                println!("Worker {} got a job; executing.", id);                job();            }        });        Worker {            id,            thread,        }    }}
  • 实现execute方法
type Job = Box;//修改Job为trait对象的类别名称impl ThreadPool {    // --snip--    pub fn execute(&self, f: F)        where            F: FnOnce() + Send + 'static    {        let job = Box::new(f);        self.sender.send(job).unwrap();    }}

完整代码

src/main.rs

use std::fs;use std::io::{Read, Write};use std::net::{TcpListener, TcpStream};use std::{thread, time};use mylib::ThreadPool;fn handle_client(mut stream: TcpStream) {    let mut buffer = [0; 512];    stream.read(&mut buffer).unwrap();    let get = b"GET / HTTP/1.1\r\n";    let (status_line, filename) = if buffer.starts_with(get) {        ("HTTP/1.1 200 OK\r\n\r\n", "main.html")    } else {        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")    };    let contents = fs::read_to_string(filename).unwrap();    let response = format!("{}{}", status_line, contents);    stream.write(response.as_bytes()).unwrap();    stream.flush().unwrap();    let ten_millis = time::Duration::from_millis(10000);    thread::sleep(ten_millis);}fn main() -> std::io::Result<()> {    let listener = TcpListener::bind("127.0.0.1:8080")?;    // let mut thread_vec: Vec> = Vec::new();    let pool = ThreadPool::new(4);    for stream in listener.incoming() {        // // handle_client(stream?);        let stream = stream.unwrap();        // let handle = thread::spawn(|| {        //     handle_client(stream);        // });        // thread_vec.push(handle);        pool.execute(|| {            handle_client(stream);        });    }    // for handle in thread_vec {    //     handle.join().unwrap();    // }        Ok(())}

src/mylib/lib.rs

use std::thread;use std::sync::mpsc;use std::sync::Arc;use std::sync::Mutex;struct Worker {    id: usize,    thread: thread::JoinHandle<()>,}impl Worker {    // fn new(id: usize) -> Worker {    //     let thread = thread::spawn(|| {});    //     Worker {    //         id,    //         thread,    //     }    // }    // fn new(id: usize, receiver: mpsc::Receiver) -> Worker {    //     let thread = thread::spawn(|| {    //         receiver;    //     });    //     Worker {    //         id,    //         thread,    //     }    // }    fn new(id: usize, receiver: Arc>>) -> Worker {        let thread = thread::spawn(move || {            loop {                let job = receiver.lock().unwrap().recv().unwrap();                println!("Worker {} got a job; executing.", id);                job();            }        });        Worker {            id,            thread,        }    }}pub struct ThreadPool {    workers: Vec,    sender: mpsc::Sender,}// struct Job;type Job = Box;//修改Job为trait对象的类别名称impl ThreadPool {    pub fn new(size: usize) -> ThreadPool {        assert!(size > 0);        // let mut threads = Vec::with_capacity(size);        // for _ in 0..size {        //     //创建线程:        //     //问题来了,创建线程的时候需要传入闭包,也就是具体做的动作,        //     //可是这个时候我们还没有具体的任务,怎么办?        // }                    // ThreadPool {        //     threads        // }        let (sender, receiver) = mpsc::channel();        let receiver = Arc::new(Mutex::new(receiver));        let mut workers = Vec::with_capacity(size);            for id in 0..size {            //workers.push(Worker::new(id));            //workers.push(Worker::new(id, receiver));            workers.push(Worker::new(id, Arc::clone(&receiver)));        }            ThreadPool {            workers,            sender,        }    }       pub fn execute(&self, f: F)        where            F: FnOnce() + Send + 'static    {        let job = Box::new(f);        self.sender.send(job).unwrap();    }}

在main的Cargo.toml添加如下依赖:

[dependencies]mylib = {path = "./mylib"}

当前版本存在的问题

线程池中的线程怎么结束?

想知道如何解决这个问题,请关注令狐一冲,下回为您分解。

你可能感兴趣的:(用Rust实现一个多线程的web server)