Rust从入门到实战系列二百五十二:使用通道向线程发送请求

下一个需要解决的问题是传递给 thread::spawn 的闭包完全没有做任何工作。目前,我们在 execute 方法
中获得期望执行的闭包,不过在创建 ThreadPool 的过程中创建每一个 Worker 时需要向 thread::spawn
传递一个闭包。
我们希望刚创建的 Worker 结构体能够从 ThreadPool 的队列中获取需要执行的代码,并发送到线程中
执行他们。
在第十六章,我们学习了 通道 —— 一个沟通两个线程的简单手段 —— 对于这个例子来说则是绝佳的。
这里通道将充当任务队列的作用,execute 将通过 ThreadPool 向其中线程正在寻找工作的 Worker 实
例发送任务。如下是这个计划:

  1. ThreadPool 会创建一个通道并充当发送端。
  2. 每个 Worker 将会充当通道的接收端。
  3. 新建一个 Job 结构体来存放用于向通道中发送的闭包。
  4. execute 方法会在通道发送端发出期望执行的任务。
  5. 在线程中,Worker 会遍历通道的接收端并执行任何接收到的任务。
    让我们以在 ThreadPool::new 中创建通道并让 ThreadPool 实例充当发送端开始,如示例 20-16 所示。
    Job 是将在通道中发出的类型,目前它是一个没有任何内容的结构体:
    文件名: src∕lib.rs

use std::thread;

// --snip–
use std::sync::mpsc;
pub struct ThreadPool {
workers: Vec,
sender: mpsc::Sender,
}
struct Job;
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);
20.2. 将单线程 SERVER 变为多线程 SERVER 547
let (sender, receiver) = mpsc::channel();
let mut workers = Vec::with_capacity(size);
for id in 0…size {
workers.push(Worker::new(id));
}
ThreadPool { workers, sender }
}
// --snip–

pub fn execute(&self, f: F)

where

F: FnOnce() + Send + 'static,

{

}

}

struct Worker {

id: usize,

thread: thread::JoinHandle<()>,

}

impl Worker {

fn new(id: usize) -> Worker {

let thread = thread::spawn(|| {});

Worker { id, thread }

}

}

示例 20-16: 修改 ThreadPool 来储存一个发送 Job 实例的通道发送端
在 ThreadPool::new 中,新建了一个通道,并接着让线程池在接收端等待。这段代码能够编译,不过仍
有警告。
让我们尝试在线程池创建每个 worker 时将通道的接收端传递给他们。须知我们希望在 worker 所分配
的线程中使用通道的接收端,所以将在闭包中引用 receiver 参数。示例 20-17 中展示的代码还不能编译:
文件名: src∕lib.rs

use std::sync::mpsc;

use std::thread;

pub struct ThreadPool {

workers: Vec,

sender: mpsc::Sender,

}

struct Job;

impl ThreadPool {
// --snip–

/// Create a new ThreadPool.

///

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

///

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

/// # 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 mut workers = Vec::with_capacity(size);
for id in 0…size {
workers.push(Worker::new(id, receiver));
}
ThreadPool { workers, sender }
}
// --snip–

pub fn execute(&self, f: F)

where

F: FnOnce() + Send + 'static,

{

}

}
// --snip–

struct Worker {

id: usize,

thread: thread::JoinHandle<()>,

}

impl Worker {
fn new(id: usize, receiver: mpsc::Receiver) -> Worker {
let thread = thread::spawn(|| {
receiver;
});
Worker { id, thread }
}
}
示例 20-17: 将通道的接收端传递给 worker
这是一些小而直观的修改:将通道的接收端传递进了 Worker::new,并接着在闭包中使用它。
如果尝试 check 代码,会得到这个错误:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
error[E0382]: use of moved value: receiver
–> src/lib.rs:27:42
|
22 | let (sender, receiver) = mpsc::channel();
| -------- move occurs because receiver has type std::sync::mpsc::Receiver<Job>, which does not implement the Copy trait

20.2. 将单线程 SERVER 变为多线程 SERVER 549
27 | workers.push(Worker::new(id, receiver));
| ^^^^^^^^ value moved here, in previous iteration of loop
For more information about this error, try rustc --explain E0382.
error: could not compile hello due to previous error
这段代码尝试将 receiver 传递给多个 Worker 实例。这是不行的,回忆第十六章:Rust 所提供的通道实
现是多 生产者,单 消费者的。这意味着不能简单的克隆通道的消费端来解决问题。即便可以,那也不是
我们希望使用的技术;我们希望通过在所有的 worker 中共享单一 receiver,在线程间分发任务。
另外,从通道队列中取出任务涉及到修改 receiver,所以这些线程需要一个能安全的共享和修改 receiver
的方式,否则可能导致竞争状态(参考第十六章)。
回忆一下第十六章讨论的线程安全智能指针,为了在多个线程间共享所有权并允许线程修改其值,需要
使用 Arc<Mutex>。Arc 使得多个 worker 拥有接收端,而 Mutex 则确保一次只有一个 worker 能从
接收端得到任务。示例 20-18 展示了所需的修改:
文件名: src∕lib.rs

use std::sync::mpsc;

use std::thread;

use std::sync::Arc;
use std::sync::Mutex;
// --snip–

pub struct ThreadPool {

workers: Vec,

sender: mpsc::Sender,

}

struct Job;

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 }
}
550 CHAPTER 20. 最后的项目: 构建多线程 WEB SERVER
// --snip–

pub fn execute(&self, f: F)

where

F: FnOnce() + Send + 'static,

{

}

}
// --snip–

struct Worker {

id: usize,

thread: thread::JoinHandle<()>,

}

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

let thread = thread::spawn(|| {

receiver;

});

Worker { id, thread }

}
}
示例 20-18: 使用 Arc 和 Mutex 在 worker 间共享通道的接收端
在 ThreadPool::new 中,将通道的接收端放入一个 Arc 和一个 Mutex 中。对于每一个新 worker,克隆
Arc 来增加引用计数,如此这些 worker 就可以共享接收端的所有权了。
通过这些修改,代码可以编译了!我们做到了!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值