用 rust
编写一个 http
服务器
rust
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080")?;
let pool = ThreadPool::new(4);
for stream in listener.incoming().take(4) {
let stream = stream.unwrap();
pool.execute(move || handle_client(stream));
}
Ok(())
}
listener.incoming()
返回一个迭代器,可以持续不断地接受新的 TCP
连接。这个迭代器理论上是无限的,会一直等待并接受新的连接
.take(4)
是对这个迭代器的限制操作,最多接受 4
个客户端连接
线程池创建
创建一个线程池,有两个属性 workers
和 sender
,workers
是一个 Vec
,存放所有的工作线程,sender
是一个 mpsc::Sender<Message>
,用于发送任务给工作线程
rust
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Message>,
}
给这个结构体创建两个关联函数 new
和 execute
,new
用于创建一个新的线程池,execute
用于向线程池中的工作线程发送任务
rust
impl ThreadPool {
pub fn new(size: usize) -> Self {
assert!(size > 0);
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, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(Message::NewJob(job)).unwrap();
}
}
assert!
assert!
这个宏,主要用于开发和测试阶段,断言失败会导致程序 panic
在 release
模式下,assert!
会被移除,不会对程序产生任何影响,所以不要在 assert!
中写任何可能会影响程序逻辑的代码
在生产环境中,应该使用适当的错误处理机制(如 Result
或 Option
)
mpsc
mpsc
(Multiple Producer, Single Consumer) 是 Rust
标准库提供的一个多生产者单消费者通道
rust
let (sender, receiver) = mpsc::channel();
sender
: 发送端,可以克隆(多个生产者)receiver
: 接收端,不能克隆(单个消费者)
- 基本使用
rust
fn main() {
// 创建一个通道,返回发送者和接收者
let (sender, receiver) = mpsc::channel();
// 创建一个新线程
thread::spawn(move || {
let messages = vec!["你好", "世界", "!"];
// 发送端发送数据
for msg in messages {
sender.send(msg).unwrap();
}
});
// 主线程接收数据
for received in receiver {
println!("收到: {}", received);
}
}
- 多线程发送者,
sender
可以被克隆为sender1
和sender2
,然用不同的线程发送消息,在receiver
中接收消息
rust
fn main() {
let (sender, receiver) = mpsc::channel();
// 克隆发送者
let sender1 = sender.clone();
let sender2 = sender.clone();
// 线程1
thread::spawn(move || {
sender1.send("来自线程1").unwrap();
});
// 线程2
thread::spawn(move || {
sender2.send("来自线程2").unwrap();
});
// 原始发送者
sender.send("来自主线程").unwrap();
// 接收消息
for _ in 0..3 {
println!("{}", receiver.recv().unwrap());
}
}
- 同步发送
使用 sync_channel
创建一个同步通道,接收一个缓冲区,当缓冲区满时,生产者会被阻塞直到有空间
rust
fn main() {
// 创建一个容量为 2 的缓冲通道
let (sender, receiver) = mpsc::sync_channel(2);
// 生产者线程
thread::spawn(move || {
for i in 1..=5 {
println!("生产者: 正在发送数据 {}", i);
sender.send(i).unwrap();
println!("生产者: 数据 {} 已发送", i);
}
});
// 消费者线程故意慢一点处理
thread::spawn(move || {
for msg in receiver {
println!("消费者: 收到数据 {}", msg);
// 模拟处理数据的耗时
thread::sleep(Duration::from_secs(1));
}
});
// 让主线程等待一会
thread::sleep(Duration::from_secs(6));
}
- 多线程同步发送
rust
fn main() {
// 创建一个容量为 2 的缓冲通道
let (sender, receiver) = mpsc::sync_channel(2);
// 创建多个生产者
for id in 1..=3 {
let sender = sender.clone();
thread::spawn(move || {
for i in 1..=3 {
let data = format!("生产者{}-数据{}", id, i);
println!("{}: 准备发送", data);
sender.send(data.clone()).unwrap();
println!("{}: 已发送", data);
thread::sleep(Duration::from_millis(1500));
}
});
}
// 丢弃原始sender
drop(sender);
// 消费者
let consumer = thread::spawn(move || {
for received in receiver {
println!("消费者: 正在处理 {}", received);
thread::sleep(Duration::from_secs(1));
println!("消费者: 处理完成 {}", received);
}
});
// 等待消费者处理完所有数据
consumer.join().unwrap();
}
- 当使用了
sender.clone()
后需要显示调用drop(sender)
,否则receiver
会一直等待
execute
execute
方法接受一个闭包,将其包装为 Box
,然后发送给工作线程
这个闭包的类型是 F
,它是一个泛型参数,有三个约束条件
FnOnce()
: 闭包没有参数,没有返回值FnOnce
表示这个函数在执行时会消耗掉自己(只能调用一次)()
表示这个函数没有参数
Send
: 闭包可以跨线程传递- 这是
Rust
并发安全的一个重要特质 - 允许这个值在线程间安全移动所有权
- 这是
'static
: 闭包的生命周期是静态的- 意味着这个值可以存活任意长的时间
- 通常用于需要长期存储或在线程间传递的值
rust
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(Message::NewJob(job)).unwrap();
}
这里 where
的意思是泛型约束,等同于
rust
pub fn execute<F: FnOnce() + Send + 'static>(&self, f: F) {
let job = Box::new(f);
self.sender.send(Message::NewJob(job)).unwrap();
}
创建工作线程
Worker
是一个工作线程,有两个属性 id
和 thread
,id
是线程的标识,thread
是一个 Option<thread::JoinHandle<()>>
,用于存放线程句柄
rust
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
给 Worker
创建一个关联函数 new
,用于创建一个新的工作线程
因为 receiver
是个 Mutex
类型,所以需要调用 lock
方法获取锁,然后调用 recv
方法接收消息
消息类型 Message
是一个枚举类型,有两个成员 NewJob
和 Terminate
,NewJob
用于接收新的任务,Terminate
用于终止线程
rust
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv().unwrap();
match message {
Message::NewJob(job) => {
println!("Worker {} receive a job.", id);
job();
}
Message::Terminate => {
println!("Worker {} receive terminate.", id);
break;
}
}
});
Worker {
id,
thread: Some(thread),
}
}
}
发送消息的消息的格式 Message::NewJob(job)
,结束表示的消息格式 Message::Terminate
rust
type Job = Box<dyn FnOnce() + Send + 'static>;
enum Message {
NewJob(Job),
Terminate,
}
通过 match
匹配消息类型,如果是 NewJob
类型,就执行闭包,如果是 Terminate
类型,就退出循环
Drop
线程池创建后,如何优雅的关闭线程呢
rust
提供了一个 Drop trait
用于在值离开作用域时执行清理工作
我们给 ThreadPool
实现 Drop trait
,当线程池离开作用域时,会自动调用 Drop trait
的 drop
方法
遍历所有 workers
,向 sender
发送 Terminate
消息,然后等待所有线程结束,最后调用 join
方法等待线程结束
rust
impl Drop for ThreadPool {
fn drop(&mut self) {
for _ in &mut self.workers {
self.sender.send(Message::Terminate).unwrap();
}
for worker in &mut self.workers {
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
这里要注意的是这两个 for
循环不能合并,因为合并后,可能会出现这种情况:
- 发送一个终止信号
- 立即等待该线程结束
- 但其他线程还没收到终止信号
这就有可能导致死锁,所以这里必须保持两个独立的循环
源码
- http-server-thread-pool
mpsc::sync_channel
使用