借用检查器并共享 I/O
Borrow checker and shared I/O
我在我的代码中遇到了一个问题,多个结构需要将数据发送到一个共享的输出接收器,而借用检查器不喜欢它。
struct SharedWriter {
count: u32,
}
impl SharedWriter {
pub fn write(&mut self) {
self.count += 1;
}
}
struct Container<'a> {
writer: &'a mut SharedWriter,
}
impl Container<'_> {
pub fn write(&mut self) {
self.writer.write();
}
}
pub fn test() {
let mut writer = SharedWriter { count: 0 };
let mut c0 = Container {
writer: &mut writer,
};
let mut c1 = Container {
// compiler chokes here with:
// cannot borrow `writer` as mutable more than once at a time
writer: &mut writer,
};
c0.write();
c1.write();
}
我了解问题及其发生的原因;你不能一次多次借用可变的东西。
我不明白的是一个很好的通用解决方案。这种模式经常发生。您有一个通用的输出接收器,如文件、套接字或数据库,并且您想要向其提供多个数据流。如果它保持任何状态,它就必须是可变的。如果它拥有任何资源,它必须只是一个单一的实体。
您可以在每个单独的 write() 方法 (write(&mut writer, some_data)
) 中传递对接收器的引用,但这会使代码混乱并且每秒将被调用(在我的特定应用程序中)数百万次。我推测一遍又一遍地传递这个参数会有一些额外的开销。
是否有一些语法可以解决这个问题?
对于您的情况,最简单的方法可能是使用 RefCell
。它会有一些运行时开销,但它是安全的。
use std::cell::RefCell;
struct SharedWriter {
count: RefCell<u32>,
}
impl SharedWriter {
pub fn new(count: u32) -> Self {
Self { count: RefCell::new(count) }
}
pub fn write(&self) {
*self.count.borrow_mut() += 1;
}
}
如果数据是 Copy
(如 u32
,以防这是您的真实数据),您可能需要使用 Cell
。适用类型较少但zero-cost:
use std::cell::Cell;
struct SharedWriter {
count: Cell<u32>,
}
impl SharedWriter {
pub fn new(count: u32) -> Self {
Self { count: Cell::new(count) }
}
pub fn write(&self) {
self.count.set(self.count.get() + 1);
}
}
有更多内部可变性原语(例如,UnsafeCell
用于 zero-cost 但不安全访问,或用于线程安全突变的互斥锁和原子)。
一种选择是使用频道。这是一个看起来如何的例子。这还有一个额外的好处,那就是允许您使用 io.js 跨多个线程进行扩展。它需要一个 handler
,它在新线程上循环运行。它会阻塞,直到收到通过发送方发送的值,然后使用给定值和对处理程序的可变引用调用 func
。当所有发送者都被删除时,线程退出。然而,这种方法的一个缺点是通道只能在一个方向上工作。
use std::sync::mpsc::{channel, Sender};
use std::thread::{self, JoinHandle};
pub fn create_shared_io<T, H, F>(mut handler: H, mut func: F) -> (JoinHandle<H>, Sender<T>)
where
T: 'static + Send,
H: 'static + Send,
F: 'static + FnMut(&mut H, T) + Send,
{
let (send, recv) = channel();
let join_handle = thread::spawn(move || loop {
let value = match recv.recv() {
Ok(v) => v,
Err(_) => break handler,
};
func(&mut handler, value);
});
(join_handle, send)
}
然后就可以像你的例子一样使用了。由于您的示例中未传递任何数据,因此它发送 ()
作为占位符。
pub fn main() {
let writer = SharedWriter { count: 0 };
println!("Starting!");
let (join_handle, sender) = create_shared_io(writer, |writer, _| {
writer.count += 1;
println!("Current count: {}", writer.count);
});
let mut c0 = Container {
writer: sender.clone(),
};
let mut c1 = Container {
writer: sender,
};
c0.write();
c1.write();
// Ensure the senders are dropped before we join the io thread to avoid possible deadlock
// where the compiler attempts to drop these values after the join.
std::mem::drop((c0, c1));
// Writer is returned when the thread is joined
let writer = join_handle.join().unwrap();
println!("Finished!");
}
struct SharedWriter {
count: u32,
}
struct Container {
writer: Sender<()>,
}
impl Container {
pub fn write(&mut self) {
self.writer.send(()).unwrap();
}
}
我在我的代码中遇到了一个问题,多个结构需要将数据发送到一个共享的输出接收器,而借用检查器不喜欢它。
struct SharedWriter {
count: u32,
}
impl SharedWriter {
pub fn write(&mut self) {
self.count += 1;
}
}
struct Container<'a> {
writer: &'a mut SharedWriter,
}
impl Container<'_> {
pub fn write(&mut self) {
self.writer.write();
}
}
pub fn test() {
let mut writer = SharedWriter { count: 0 };
let mut c0 = Container {
writer: &mut writer,
};
let mut c1 = Container {
// compiler chokes here with:
// cannot borrow `writer` as mutable more than once at a time
writer: &mut writer,
};
c0.write();
c1.write();
}
我了解问题及其发生的原因;你不能一次多次借用可变的东西。
我不明白的是一个很好的通用解决方案。这种模式经常发生。您有一个通用的输出接收器,如文件、套接字或数据库,并且您想要向其提供多个数据流。如果它保持任何状态,它就必须是可变的。如果它拥有任何资源,它必须只是一个单一的实体。
您可以在每个单独的 write() 方法 (write(&mut writer, some_data)
) 中传递对接收器的引用,但这会使代码混乱并且每秒将被调用(在我的特定应用程序中)数百万次。我推测一遍又一遍地传递这个参数会有一些额外的开销。
是否有一些语法可以解决这个问题?
对于您的情况,最简单的方法可能是使用 RefCell
。它会有一些运行时开销,但它是安全的。
use std::cell::RefCell;
struct SharedWriter {
count: RefCell<u32>,
}
impl SharedWriter {
pub fn new(count: u32) -> Self {
Self { count: RefCell::new(count) }
}
pub fn write(&self) {
*self.count.borrow_mut() += 1;
}
}
如果数据是 Copy
(如 u32
,以防这是您的真实数据),您可能需要使用 Cell
。适用类型较少但zero-cost:
use std::cell::Cell;
struct SharedWriter {
count: Cell<u32>,
}
impl SharedWriter {
pub fn new(count: u32) -> Self {
Self { count: Cell::new(count) }
}
pub fn write(&self) {
self.count.set(self.count.get() + 1);
}
}
有更多内部可变性原语(例如,UnsafeCell
用于 zero-cost 但不安全访问,或用于线程安全突变的互斥锁和原子)。
一种选择是使用频道。这是一个看起来如何的例子。这还有一个额外的好处,那就是允许您使用 io.js 跨多个线程进行扩展。它需要一个 handler
,它在新线程上循环运行。它会阻塞,直到收到通过发送方发送的值,然后使用给定值和对处理程序的可变引用调用 func
。当所有发送者都被删除时,线程退出。然而,这种方法的一个缺点是通道只能在一个方向上工作。
use std::sync::mpsc::{channel, Sender};
use std::thread::{self, JoinHandle};
pub fn create_shared_io<T, H, F>(mut handler: H, mut func: F) -> (JoinHandle<H>, Sender<T>)
where
T: 'static + Send,
H: 'static + Send,
F: 'static + FnMut(&mut H, T) + Send,
{
let (send, recv) = channel();
let join_handle = thread::spawn(move || loop {
let value = match recv.recv() {
Ok(v) => v,
Err(_) => break handler,
};
func(&mut handler, value);
});
(join_handle, send)
}
然后就可以像你的例子一样使用了。由于您的示例中未传递任何数据,因此它发送 ()
作为占位符。
pub fn main() {
let writer = SharedWriter { count: 0 };
println!("Starting!");
let (join_handle, sender) = create_shared_io(writer, |writer, _| {
writer.count += 1;
println!("Current count: {}", writer.count);
});
let mut c0 = Container {
writer: sender.clone(),
};
let mut c1 = Container {
writer: sender,
};
c0.write();
c1.write();
// Ensure the senders are dropped before we join the io thread to avoid possible deadlock
// where the compiler attempts to drop these values after the join.
std::mem::drop((c0, c1));
// Writer is returned when the thread is joined
let writer = join_handle.join().unwrap();
println!("Finished!");
}
struct SharedWriter {
count: u32,
}
struct Container {
writer: Sender<()>,
}
impl Container {
pub fn write(&mut self) {
self.writer.send(()).unwrap();
}
}