【发布时间】:2014-12-06 23:53:35
【问题描述】:
我正在尝试用 Rust 编写 union-find 的实现。众所周知,这在 C 等语言中实现起来非常简单,同时仍然具有复杂的运行时分析。
我在获取 Rust 的互斥体语义以允许迭代手动锁定时遇到问题。
这就是我现在的位置。
首先,这是我在 C 中想要的部分结构的非常简单的实现:
#include <stdlib.h>
struct node {
struct node * parent;
};
struct node * create(struct node * parent) {
struct node * ans = malloc(sizeof(struct node));
ans->parent = parent;
return ans;
}
struct node * find_root(struct node * x) {
while (x->parent) {
x = x->parent;
}
return x;
}
int main() {
struct node * foo = create(NULL);
struct node * bar = create(foo);
struct node * baz = create(bar);
baz->parent = find_root(bar);
}
注意指针的结构是一个倒置的树;多个指针可能指向一个位置,并且没有循环。
此时,没有路径压缩。
这是一个 Rust 翻译。我选择使用 Rust 的引用计数指针类型来支持我上面引用的倒排树类型。
请注意,此实现更加冗长,可能是由于 Rust 提供了更高的安全性,但也可能是由于我对 Rust 缺乏经验。
use std::rc::Rc;
struct Node {
parent: Option<Rc<Node>>
}
fn create(parent: Option<Rc<Node>>) -> Node {
Node {parent: parent.clone()}
}
fn find_root(x: Rc<Node>) -> Rc<Node> {
let mut ans = x.clone();
while ans.parent.is_some() {
ans = ans.parent.clone().unwrap();
}
ans
}
fn main() {
let foo = Rc::new(create(None));
let bar = Rc::new(create(Some(foo.clone())));
let mut prebaz = create(Some(bar.clone()));
prebaz.parent = Some(find_root(bar.clone()));
}
每次调用find_root 时,路径压缩都会沿到根的路径重新设置每个节点的父节点。要将这个特性添加到 C 代码中,只需要两个新的小函数:
void change_root(struct node * x, struct node * root) {
while (x) {
struct node * tmp = x->parent;
x->parent = root;
x = tmp;
}
}
struct node * root(struct node * x) {
struct node * ans = find_root(x);
change_root(x, ans);
return ans;
}
函数change_root 执行所有重新父级,而函数root 只是一个包装器,使用find_root 的结果来重新父级到根路径上的节点。
为了在 Rust 中做到这一点,我决定我必须使用 Mutex 而不仅仅是引用计数指针,因为 Rc 接口只允许在多个时通过写时复制进行可变访问指向该项目的指针是活动的。结果,所有代码都必须更改。在进入路径压缩部分之前,我就挂断了find_root:
use std::sync::{Mutex,Arc};
struct Node {
parent: Option<Arc<Mutex<Node>>>
}
fn create(parent: Option<Arc<Mutex<Node>>>) -> Node {
Node {parent: parent.clone()}
}
fn find_root(x: Arc<Mutex<Node>>) -> Arc<Mutex<Node>> {
let mut ans = x.clone();
let mut inner = ans.lock();
while inner.parent.is_some() {
ans = inner.parent.clone().unwrap();
inner = ans.lock();
}
ans.clone()
}
这会产生错误(使用 0.12.0)
error: cannot assign to `ans` because it is borrowed
ans = inner.parent.clone().unwrap();
note: borrow of `ans` occurs here
let mut inner = ans.lock();
我认为我需要的是手动锁定。对于路径 A -> B -> C -> ...,我需要锁定 A,锁定 B,解锁 A,锁定 C,解锁 B,...当然,我可以保持所有锁打开:lock A,锁 B,锁 C,...解锁 C,解锁 B,解锁 A,但这似乎效率低下。
但是,Mutex 不提供解锁功能,而是使用 RAII。 如果不能直接调用 unlock,如何在 Rust 中实现手动锁定?
编辑:正如 cmets 所说,我可以使用 Rc<RefCell<Node>> 而不是 Arc<Mutex<Node>>。这样做会导致相同的编译器错误。
为了清楚说明我试图通过使用手动锁定来避免什么,这里有一个 RefCell 版本,它可以编译但使用的空间与路径长度呈线性关系。
fn find_root(x: Rc<RefCell<Node>>) -> Rc<RefCell<Node>> {
let mut inner : RefMut<Node> = x.borrow_mut();
if inner.parent.is_some() {
find_root(inner.parent.clone().unwrap())
} else {
x.clone()
}
}
【问题讨论】:
-
不,
Mutex在这里几乎肯定是错误的选择。如果您停留在单个任务中,您可能应该(默认情况下),最好使用Rc<Cell<T>>(如果T是Copy)或Rc<RefCell<T>>(如果不是)来改变Rc后面的内容. -
我同意
Cell或RefCell是正确的途径。您有一个看起来不可变的方法 (find),但想要在幕后改变数据。这是 Cell 家族的主要案例。 -
当使用
Rc<RefCell<Node>>代替Arc<Mutex<Node>>(和borrow_mut代替lock)时,我得到完全相同的错误:“错误:无法分配给ans,因为它借来的……”
标签: data-structures locking rust