文章目录
前言
在经过前期对闭包,互斥器,智能指针,线程,通道和Tcp服务的学习后,笔者开始试着跟着视频写一个简单的web server,其中实现了线程池,让整个进程更加合理的分配线程。
以下是我在实现过程中的一些心得和遇到的问题。
web server实现
接下来我会直接给出代码并在注释中进行解释,以及对编写过程中遇到的问题进行讨论。
lib.rs:
use std::thread;
//导入线程包
use std::sync::mpsc;
//多个生产者单个消费者
use std::sync::Arc;
//智能指针
use std::sync::Mutex;
//互斥器
type Job = Box<dyn FnOnce() + Send + 'static>;
//定义传回闭包的特征
enum Message {
//使用枚举类型定义在web server中的消息类型
NewJob(Job),
//收到的任务
Terminate,
//中断消息
}
struct Worker {
id:usize,
//worker的编号
thread:Option<thread::JoinHandle<()>>,
//worker中内置的线程
}
impl Worker {
//定义worker的方法
fn new(id:usize,recevier:Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
//定义worker的初始化方法
//由于worker是内置的,所以默认为私有类型
let thread = thread::spawn(move || {
//定义内置线程
loop {
//让worker的内置线程不断循环,始终保持运行态
//let job = recevier.lock().unwrap().recv().unwrap();
//println!("Worker {} get a job!",id);
//job();
//如上是无线程池配置时的线程任务
//无论有无收到任务,线程都是开启的,并且由recv()函数的特性,线程也一直处于阻塞态
//这是一种非常浪费资源的安排,所以引入drop特性,对线程进行数量限制以及终止,释放空闲资源
//match recevier.lock().unwrap().recv().unwrap() {
let message = recevier.lock().unwrap().recv().unwrap();
//疑问:当如上直接match时,进程会卡在某一个worker上,为什么?
match message {
//在这里对接收到的消息进行类型匹配
Message::NewJob(job) => {
//当接收到任务时
println!("worker {} get a job!",id);
job()
//从Option Some(Job)中取出Job并执行闭包中的任务
},
Message::Terminate => {
//接收到中断消息
println!("worker {} terminat!",id);
break;
//终止线程
},
}
}
});
Worker {
//返回worker结构体
id,
thread:Some(thread),
}
}
}
pub struct ThreadPool {
//定义线程池结构体具体结构
workers: Vec<Worker>,
//定义内置类型为worker的vec容器
sender: mpsc::Sender<Message>,
//为线程池配置发送端,以便对worker进行调度
}
impl ThreadPool {
//为线程池结构体定义方法
pub fn new(size:usize) -> ThreadPool {
//定义初始化函数
let (sender, recevier) = mpsc::channel();
//创建通道的发送端和接收端
let recevier = Arc::new(Mutex::new(recevier));
//将recevier放入智能指针Arc中并使用Mutex互斥器,保证线程安全
let mut workers = Vec::with_capacity(size);
//根据线程池中预设的线程总数定义vec容器
for id in 0..size {
//将worker放入提前定义的ec容器中
workers.push (Worker::new(id,Arc::clone(&recevier)));
//注意要使用clone
ThreadPool {
//返回线程池结构体
workers,
sender,
}
}
pub fn execute<F>(&self, f:F) where F: FnOnce() + Send + 'static {
//定义 execute 方法
let job = Box::new(f);
//传入的任务放入box智能指针中
self.sender.send(Message::NewJob(job)).unwrap();
//将任务发送进通道等待recv接收
}
}
impl Drop for ThreadPool {
//为线程池结构体配置drop特性
fn drop(&mut self) {
//首先向线程发送drop消息
for _ in &self.workers {
self.sender.send(Message::Terminate).unwrap();
}
for worker in &mut self.workers{
//等待线程结束
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
main.rs:
use std::thread;
use std::time::Duration;
use std::io::prelude::*;
use std::net::TcpStream;
use std::fs::File;
use std::fs;
use std::net::TcpListener;
use mylib::ThreadPool;
fn handle_connection(mut stream: TcpStream) {
//定义响应方法
let mut buffer = [0; 512];
stream.read(&mut buffer).unwrap();
let get = b"GET / HTTP/1.1\r\n";
let sleep = b"GET /sleep HTTP/1.1\r\n";
//定义检测前缀
let (status_line, filename) = if buffer.starts_with(get) {
//定义返回状态和返回页面文件名
("HTTP/1.1 200 OK\r\n\r\n", "test.html")
} else if buffer.starts_with(sleep) {
thread::sleep(Duration::from_secs(25));
("HTTP/1.1 200 OK\r\n\r\n", "test.html")
} else {
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
};
let content = fs::read_to_string(filename).unwrap();
let response = format!("{}{}",status_line,content);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
//thread::sleep(Duration::from_secs(10));
}
fn main() {
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
let pool = ThreadPool::new(4);
for stream in listener.incoming().take(4) {
let stream = stream.unwrap();
pool.execute(move || {
handle_connection(stream)
});
}
}