作者热门文章
- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在尝试在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);
}
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
的结果来重新父级到根路径上的节点的包装器。
Mutex
而不是引用计数的指针,因为
Rc
接口(interface)仅在有多个指向该项目的指针存在时才允许通过写时复制进行可变访问。结果,所有代码都必须更改。甚至在进入路径压缩部分之前,我都卡在
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()
}
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();
Mutex
不提供解锁功能,而是使用RAII。
如何在Rust中实现交接锁定而又不能直接调用unlock
?
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()
}
}
最佳答案
当我们仅使用unsafe
遍历此列表时,我们可以很容易地进行完全交接锁定,这对于告诉借阅检查器我们知道的一小部分见解是必要的,但它不知道。
但是首先,让我们清楚地阐明问题:
Arc<Mutex<Node>>
,以获取列表中的最后一个节点fn find_root(node: Arc<Mutex<Node>>) -> Arc<Mutex<Node>>
;
fn find_root(incoming: Arc<Mutex<Node>>) -> Arc<Mutex<Node>> {
// We have to separate this from incoming since the lock must
// be borrowed from incoming, not this local node.
let mut node = incoming.clone();
let mut lock = incoming.lock();
// Could use while let but that leads to borrowing issues.
while lock.parent.is_some() {
node = lock.parent.as_ref().unwrap().clone(); // !! uh-oh !!
lock = node.lock();
}
node
}
!! uh-oh !!
的行上出错,告诉我们在
lock
仍然存在的情况下我们无法移出节点,因为
lock
借用了
node
。这不是伪造的错误!
lock
中的数据可能会在
node
中消失后立即消失-这仅仅是因为我们知道即使移动
lock
可以解决此问题,我们也可以将
node
指向的数据保持有效并位于同一内存位置。
Arc
中包含的数据的生命周期是动态的,并且借位检查器很难就
Arc
中的数据有效时间进行推断。
unsafe
-我们告诉编译器我们不仅仅了解的方法,它应该使我们可以将我们知道但不知道的保证告知它。
mem::transmute
,该函数允许我们重新解释任何变量的类型,只需使用它即可将节点返回的锁的生存期更改为比实际更长。
lock
不指向节点,而是指向
node
内部的数据,该地址的地址(在这种情况下,因为它位于
Arc
后面)不会改变。
Arc
。借用检查器会警告我们可能存在严重错误-如果
Mutex
保持内联而不是
Arc
中,则此错误将是对Free-After-Free的正确使用,因为
MutexGuard
中包含的
lock
会尝试解锁已被删除或至少已移动到另一个存储位置的
Mutex
。
use std::mem;
use std::sync::{Arc, Mutex};
fn find_root(incoming: Arc<Mutex<Node>>) -> Arc<Mutex<Node>> {
let mut node = incoming.clone();
let mut handoff_node;
let mut lock = incoming.lock();
// Could use while let but that leads to borrowing issues.
while lock.parent.is_some() {
// Keep the data in node around by holding on to this `Arc`.
handoff_node = node;
node = lock.parent.as_ref().unwrap().clone();
// We are going to move out of node while this lock is still around,
// but since we kept the data around it's ok.
lock = unsafe { mem::transmute(node.lock()) };
}
node
}
lock
的分配中,在获取新锁之前释放旧锁的情况。要解决此问题,只需再添加一个
holdover_lock
变量,然后在重新分配之前将旧锁移入其中,然后在重新分配
lock
之后将其删除,这是很简单的。
unsafe
来解决借阅检查器中的“缺陷”。我仍然希望您很少了解借用检查器的情况,并且更改生命周期不是“通常”的行为。
Mutex
相当复杂,您必须处理许多很多可能的竞争条件源,而我什至没有捕获所有这些源!除非确实需要从多个线程访问此结构,否则最好使用
Rc
和
RefCell
,因为这样会使事情变得容易得多。
关于data-structures - 用Rust进行交接锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27337939/
我是一名优秀的程序员,十分优秀!