Rust从入门到实战系列二百五十三:实现 execute 方法

最后让我们实现 ThreadPool 上的 execute 方法。同时也要修改 Job 结构体:它将不再是结构体,Job 将
是一个有着 execute 接收到的闭包类型的 trait 对象的类型别名。第十九章 ” 类型别名用来创建类型同义
词” 部分提到过,类型别名允许将长的类型变短。观察示例 20-19:
文件名: src∕lib.rs

use std::sync::mpsc;

use std::sync::Arc;

use std::sync::Mutex;

use std::thread;

pub struct ThreadPool {

workers: Vec,

sender: mpsc::Sender,

}

// --snip–
type Job = Box<dyn FnOnce() + Send + 'static>;
20.2. 将单线程 SERVER 变为多线程 SERVER 551
impl ThreadPool {
// --snip–

/// Create a new ThreadPool.

///

/// The size is the number of threads in the pool.

///

/// # Panics

///

/// The new function will panic if the size is zero.

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

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(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}
// --snip–

struct Worker {

id: usize,

thread: thread::JoinHandle<()>,

}

impl Worker {

fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver>>) -> Worker {

let thread = thread::spawn(|| {

receiver;

});

Worker { id, thread }

}

}

示例 20-19: 为存放每一个闭包的 Box 创建一个 Job 类型别名,接着在通道中发出任务
在使用 execute 得到的闭包新建 Job 实例之后,将这些任务从通道的发送端发出。这里调用 send 上的
unwrap,因为发送可能会失败,这可能发生于例如停止了所有线程执行的情况,这意味着接收端停止接
收新消息了。不过目前我们无法停止线程执行;只要线程池存在他们就会一直执行。使用 unwrap 是因
为我们知道失败不可能发生,即便编译器不这么认为。
552 CHAPTER 20. 最后的项目: 构建多线程 WEB SERVER
不过到此事情还没有结束!在 worker 中,传递给 thread::spawn 的闭包仍然还只是 引用了通道的接收
端。相反我们需要闭包一直循环,向通道的接收端请求任务,并在得到任务时执行他们。如示例 20-20
对 Worker::new 做出修改:
文件名: src∕lib.rs

use std::sync::mpsc;

use std::sync::Arc;

use std::sync::Mutex;

use std::thread;

pub struct ThreadPool {

workers: Vec,

sender: mpsc::Sender,

}

type Job = Box<dyn FnOnce() + Send + 'static>;

impl ThreadPool {

/// Create a new ThreadPool.

///

/// The size is the number of threads in the pool.

///

/// # Panics

///

/// The new function will panic if the size is zero.

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

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(&self, f: F)

where

F: FnOnce() + Send + 'static,

{

let job = Box::new(f);

self.sender.send(job).unwrap();

}

}

struct Worker {

id: usize,

thread: thread::JoinHandle<()>,

}

20.2. 将单线程 SERVER 变为多线程 SERVER 553
// --snip–
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver>>) -> 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 }
}
}
示例 20-20: 在 worker 线程中接收并执行任务
这里,首先在 receiver 上调用了 lock 来获取互斥器,接着 unwrap 在出现任何错误时 panic。如果互
斥器处于一种叫做 被污染(poisoned)的状态时获取锁可能会失败,这可能发生于其他线程在持有锁时
panic 了且没有释放锁。在这种情况下,调用 unwrap 使其 panic 是正确的行为。请随意将 unwrap 改
为包含有意义错误信息的 expect。
如果锁定了互斥器,接着调用 recv 从通道中接收 Job。最后的 unwrap 也绕过了一些错误,这可能发生
于持有通道发送端的线程停止的情况,类似于如果接收端关闭时 send 方法如何返回 Err 一样。
调用 recv 会阻塞当前线程,所以如果还没有任务,其会等待直到有可用的任务。Mutex 确保一次只
有一个 Worker 线程尝试请求任务。
现在线程池处于可以运行的状态了!执行 cargo run 并发起一些请求:
$ cargo run
Compiling hello v0.1.0 (file:///projects/hello)
warning: field is never read: workers
–> src/lib.rs:7:5
|
7 | workers: Vec,
| ^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(dead_code)] on by default
warning: field is never read: id
–> src/lib.rs:48:5
|
48 | id: usize,
| ^^^^^^^^^
warning: field is never read: thread
–> src/lib.rs:49:5
|
49 | thread: thread::JoinHandle<()>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: 3 warnings emitted
Finished dev [unoptimized + debuginfo] target(s) in 1.40s
Running target/debug/main
554 CHAPTER 20. 最后的项目: 构建多线程 WEB SERVER
Worker 0 got a job; executing.
Worker 2 got a job; executing.
Worker 1 got a job; executing.
Worker 3 got a job; executing.
Worker 0 got a job; executing.
Worker 2 got a job; executing.
Worker 1 got a job; executing.
Worker 3 got a job; executing.
Worker 0 got a job; executing.
Worker 2 got a job; executing.
成功了!现在我们有了一个可以异步执行连接的线程池!它绝不会创建超过四个线程,所以当 server 收
到大量请求时系统也不会负担过重。如果请求 ∕sleep,server 也能够通过另外一个线程处理其他请求。
注意如果同时在多个浏览器窗口打开 ∕sleep,它们可能会彼此间隔地加载 5 秒,因为一些浏览器处
于缓存的原因会顺序执行相同请求的多个实例。这些限制并不是由于我们的 web server 造成的。
在学习了第十八章的 while let 循环之后,你可能会好奇为何不能如此编写 worker 线程,如示例 20-21
所示:
文件名: src∕lib.rs

use std::sync::mpsc;

use std::sync::Arc;

use std::sync::Mutex;

use std::thread;

pub struct ThreadPool {

workers: Vec,

sender: mpsc::Sender,

}

type Job = Box<dyn FnOnce() + Send + 'static>;

impl ThreadPool {

/// Create a new ThreadPool.

///

/// The size is the number of threads in the pool.

///

/// # Panics

///

/// The new function will panic if the size is zero.

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

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 }

20.2. 将单线程 SERVER 变为多线程 SERVER 555

}

pub fn execute(&self, f: F)

where

F: FnOnce() + Send + 'static,

{

let job = Box::new(f);

self.sender.send(job).unwrap();

}

}

struct Worker {

id: usize,

thread: thread::JoinHandle<()>,

}

// --snip–
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver>>) -> Worker {
let thread = thread::spawn(move || {
while let Ok(job) = receiver.lock().unwrap().recv() {
println!(“Worker {} got a job; executing.”, id);
job();
}
});
Worker { id, thread }
}
}
示例 20-21: 一个使用 while let 的 Worker::new 替代实现
这段代码可以编译和运行,但是并不会产生所期望的线程行为:一个慢请求仍然会导致其他请求等待
执行。其原因有些微妙:Mutex 结构体没有公有 unlock 方法,因为锁的所有权依赖 lock 方法返回的
LockResult<MutexGuard> 中 MutexGuard 的生命周期。这允许借用检查器在编译时确保绝不
会在没有持有锁的情况下访问由 Mutex 守护的资源,不过如果没有认真的思考 MutexGuard 的生
命周期的话,也可能会导致比预期更久的持有锁。
示例 20-20 中的代码使用的 let job = receiver.lock (). unwrap().recv().unwrap(); 之所以可以工作是因
为对于 let 来说,当 let 语句结束时任何表达式中等号右侧使用的临时值都会立即被丢弃。然而 while let
(if let 和 match)直到相关的代码块结束都不会丢弃临时值。在示例 20-21 中,job () 调用期间锁一直
持续,这也意味着其他的 worker 无法接受任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值