Rust从入门到实战系列二百四十九:采用编译器驱动构建 ThreadPool 结构体

继续并对示例 20-12 中的 src∕main.rs 做出修改,并利用来自 cargo check 的编译器错误来驱动开发。下
面是我们得到的第一个错误:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
error[E0433]: failed to resolve: use of undeclared type ThreadPool
–> src/main.rs:10:16
|
10 | let pool = ThreadPool::new(4);
| ^^^^^^^^^^ use of undeclared type ThreadPool
For more information about this error, try rustc --explain E0433.
error: could not compile hello due to previous error
好的,这告诉我们需要一个 ThreadPool 类型或模块,所以我们将构建一个。ThreadPool 的实现会与
web server 的特定工作相独立,所以让我们从 hello crate 切换到存放 ThreadPool 实现的新库 crate。
这也意味着可以在任何工作中使用这个单独的线程池库,而不仅仅是处理网络请求。
创建 src∕lib.rs 文件,它包含了目前可用的最简单的 ThreadPool 定义:
文件名: src∕lib.rs
pub struct ThreadPool;
接着创建一个新目录,src∕bin,并将二进制 crate 根文件从 src∕main.rs 移动到 src∕bin∕main.rs。这使得
库 crate 成为 hello 目录的主要 crate;不过仍然可以使用 cargo run 运行 src∕bin∕main.rs 二进制文件。
移动了 main.rs 文件之后,修改 src∕bin∕main.rs 文件开头加入如下代码来引入库 crate 并将 ThreadPool
引入作用域:
文件名: src∕bin∕main.rs
use hello::ThreadPool;

use std::fs;

use std::io::prelude:😗;

use std::net::TcpListener;

use std::net::TcpStream;

use std::thread;

use std::time::Duration;

fn main() {

let listener = TcpListener::bind(“127.0.0.1:7878”).unwrap();

let pool = ThreadPool::new(4);

540 CHAPTER 20. 最后的项目: 构建多线程 WEB SERVER

for stream in listener.incoming() {

let stream = stream.unwrap();

pool.execute(|| {

handle_connection(stream);

});

}

}

fn handle_connection(mut stream: TcpStream) {

let mut buffer = [0; 1024];

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”, “hello.html”)

} else if buffer.starts_with(sleep) {

thread::sleep(Duration::from_secs(5));

(“HTTP/1.1 200 OK”, “hello.html”)

} else {

(“HTTP/1.1 404 NOT FOUND”, “404.html”)

};

let contents = fs::read_to_string(filename).unwrap();

let response = format!(

“{}\r\nContent-Length: {}\r\n\r\n{}”,

status_line,

contents.len(),

contents

);

stream.write(response.as_bytes()).unwrap();

stream.flush().unwrap();

}

这仍然不能工作,再次尝试运行来得到下一个需要解决的错误:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
error[E0599]: no function or associated item named new found for struct ThreadPool in the current scope
–> src/bin/main.rs:11:28
|
11 | let pool = ThreadPool::new(4);
| ^^^ function or associated item not found in ThreadPool
For more information about this error, try rustc --explain E0599.
error: could not compile hello due to previous error
这告诉我们下一步是为 ThreadPool 创建一个叫做 new 的关联函数。我们还知道 new 需要有一个参数
可以接受 4,而且 new 应该返回 ThreadPool 实例。让我们实现拥有此特征的最小化 new 函数:
文件夹: src∕lib.rs
20.2. 将单线程 SERVER 变为多线程 SERVER 541
pub struct ThreadPool;
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
ThreadPool
}
}
这里选择 usize 作为 size 参数的类型,因为我们知道为负的线程数没有意义。我们还知道将使用 4 作为
线程集合的元素数量,这也就是使用 usize 类型的原因,如第三章 ” 整型” 部分所讲。
再次编译检查这段代码:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
error[E0599]: no method named execute found for struct ThreadPool in the current scope
–> src/bin/main.rs:16:14
|
16 | pool.execute(|| {
| ^^^^^^^ method not found in ThreadPool
For more information about this error, try rustc --explain E0599.
error: could not compile hello due to previous error
现在有了一个警告和一个错误。暂时先忽略警告,发生错误是因为并没有 ThreadPool 上的 execute 方
法。回忆 ” 为有限数量的线程创建一个类似的接口” 部分我们决定线程池应该有与 thread::spawn 类似
的接口,同时我们将实现 execute 函数来获取传递的闭包并将其传递给池中的空闲线程执行。
我们会在 ThreadPool 上定义 execute 函数来获取一个闭包参数。回忆第十三章的 ” 使用带有泛型和
Fn trait 的闭包” 部分,闭包作为参数时可以使用三个不同的 trait:Fn、FnMut 和 FnOnce。我们需
要决定这里应该使用哪种闭包。最终需要实现的类似于标准库的 thread::spawn,所以我们可以观察
thread::spawn 的签名在其参数中使用了何种 bound。查看文档会发现:
pub fn spawn<F, T>(f: F) -> JoinHandle
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
F 是这里我们关心的参数;T 与返回值有关所以我们并不关心。考虑到 spawn 使用 FnOnce 作为 F 的 trait
bound,这可能也是我们需要的,因为最终会将传递给 execute 的参数传给 spawn。因为处理请求的线
程只会执行闭包一次,这也进一步确认了 FnOnce 是我们需要的 trait,这里符合 FnOnce 中 Once 的意思。
F 还有 trait bound Send 和生命周期绑定 ’ static,这对我们的情况也是有意义的:需要 Send 来将闭包
从一个线程转移到另一个线程,而 ’ static 是因为并不知道线程会执行多久。让我们编写一个使用带有
这些 bound 的泛型参数 F 的 ThreadPool 的 execute 方法:
文件名: src∕lib.rs

pub struct ThreadPool;

impl ThreadPool {
// --snip–

pub fn new(size: usize) -> ThreadPool {

ThreadPool

}

pub fn execute(&self, f: F)
542 CHAPTER 20. 最后的项目: 构建多线程 WEB SERVER
where
F: FnOnce() + Send + 'static,
{
}
}
FnOnce trait 仍然需要之后的 (),因为这里的 FnOnce 代表一个没有参数也没有返回值的闭包。正如函
数的定义,返回值类型可以从签名中省略,不过即便没有参数也需要括号。
这里再一次增加了 execute 方法的最小化实现:它没有做任何工作,只是尝试让代码能够编译。再次进
行检查:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.24s
现在就只有警告了!这意味着能够编译了!注意如果尝试 cargo run 运行程序并在浏览器中发起请求,
仍会在浏览器中出现在本章开始时那样的错误。这个库实际上还没有调用传递给 execute 的闭包!
一个你可能听说过的关于像 Haskell 和 Rust 这样有严格编译器的语言的说法是 ” 如果代码能够编
译,它就能工作”。这是一个提醒大家的好时机,实际上这并不是普适的。我们的项目可以编译,不
过它完全没有做任何工作!如果构建一个真实且功能完整的项目,则需花费大量的时间来开始编写
单元测试来检查代码能否编译 并且拥有期望的行为。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值