我在内存中遇到了内存回收的问题crossbeam.假设您正在实现一个包含单个值的简单线程安全无锁容器.任何线程都可以获取存储值的克隆,并且值可以在任何时候更新,之后读者开始观察新值的克隆.
虽然典型的用例是指定像Arc<X>T 这样的东西,但实现不能依赖于T指针大小 - 例如,X可能是一个特征,导致胖指针Arc<X>.但是对任意T的无锁访问似乎非常适合基于纪元的无锁代码.根据这些例子,我想出了这个:
extern crate crossbeam;
use std::thread;
use std::sync::atomic::Ordering;
use crossbeam::epoch::{self, Atomic, Owned};
struct Container<T: Clone> {
current: Atomic<T>,
}
impl<T: Clone> Container<T> {
fn new(initial: T) -> Container<T> {
Container { current: Atomic::new(initial) }
}
fn set_current(&self, new: T) {
let guard = epoch::pin();
let prev = self.current.swap(Some(Owned::new(new)),
Ordering::AcqRel, &guard);
if let Some(prev) = prev {
unsafe {
// once swap has propagated, *PREV will no longer
// be observable
//drop(::std::ptr::read(*prev));
guard.unlinked(prev);
}
}
}
fn get_current(&self) -> T {
let guard = epoch::pin();
// clone the latest visible value
(*self.current.load(Ordering::Acquire, &guard).unwrap()).clone()
}
}
Run Code Online (Sandbox Code Playgroud)
当与不分配的类型一起使用时,例如with T=u64,它可以很好地工作 - set_current并且get_current可以被称为数百万次而不会泄漏.(过程监视器显示由于epoch伪gc 引起的微小内存振荡,如预期的那样,但没有长期增长.)但是,当T是一种分配类型时,例如Box<u64>,人们可以很容易地观察泄漏.例如:
fn main() {
use std::sync::Arc;
let c = Arc::new(Container::new(Box::new(0)));
const ITERS: u64 = 100_000_000;
let producer = thread::spawn({
let c = Arc::clone(&c);
move || {
for i in 0..ITERS {
c.set_current(Box::new(i));
}
}
});
let consumers: Vec<_> = (0..16).map(|_| {
let c = Arc::clone(&c);
thread::spawn(move || {
let mut last = 0;
loop {
let current = c.get_current();
if *current == ITERS - 1 {
break;
}
assert!(*current >= last);
last = *current;
}
})}).collect();
producer.join().unwrap();
for x in consumers {
x.join().unwrap();
}
}
Run Code Online (Sandbox Code Playgroud)
运行此程序会显示内存使用量稳定且显着增加,最终会消耗与迭代次数成比例的内存量.
根据介绍它的博客文章,Crossbeam的epoch填充"不会运行析构函数,而只是释放内存".的try_pop在堆叠二极管驱动器的示例使用ptr::read(&(*head).data)移动包含在值head.data出的head目的地为解除分配对象.数据对象的所有权将传输给调用者,调用者可以将其移动到其他地方,也可以在超出范围时解除分配.
如何转换为上面的代码?setter是适当的位置guard.unlinked,或者如何确保drop在底层对象上运行?取消注释drop(ptr::read(*prev))失败断言的显式结果,检查单调性,可能表明过早释放.
问题的关键是(正如你已经想到的那样)guard.unlinked(prev)推迟执行以下代码:
drop(Vec::from_raw_parts(prev.as_raw(), 0, 1));
Run Code Online (Sandbox Code Playgroud)
但是你希望它推迟这个:
drop(Vec::from_raw_parts(prev.as_raw(), 1, 1));
Run Code Online (Sandbox Code Playgroud)
或者,等效地:
drop(Box::from_raw(prev.as_raw());
Run Code Online (Sandbox Code Playgroud)
换句话说,unlinked只是释放存储对象的内存,但不会丢弃对象本身.
这是目前Crossbeam的一个着名的痛点,但幸运的是它很快就会得到解决.Crossbeam的基于纪元的垃圾收集器目前正在进行重新设计和重写,以便:
如果您想了解有关新Crossbeam设计的更多信息,请查看RFC库.我建议从新Atomic上的RFC和新GC上的RFC开始.
我创造了一个实验箱,Coco,与Crossbeam的新设计有很多共同之处.如果您现在需要解决方案,我建议您切换到它.但是,请记住,可可将有利于大梁的,只要我们发布一个新版本(可能是这个或下月)已过时.
| 归档时间: |
|
| 查看次数: |
481 次 |
| 最近记录: |