#hazard-pointers #reclamation #garbage-collection #shared-ptr #data-structures

无需 std 随意

使用危害指针进行动态内存管理的无锁数据结构

10 个版本

0.1.8 2023年12月17日
0.1.7 2023年10月3日
0.1.5 2023年1月14日
0.1.4 2022年4月10日
0.0.0 2022年1月30日

#74 in 内存管理


3 crates 中使用

Apache-2.0

96KB
1K SLoC

Crates.io Documentation codecov Dependency status

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1121r3.pdf https://github.com/facebook/folly/tree/0e92d3c2705a45ba7850708fd7fe0c709d6a0e5f

许可证

基于 Apache 许可证,版本 2.0 (LICENSE-APACHEhttps://apache.ac.cn/licenses/LICENSE-2.0)。

贡献

除非您明确声明,否则您提交的任何有意用于包含在作品中的贡献,根据 Apache-2.0 许可证定义,应按上述方式许可,而不附加任何额外条款或条件。


lib.rs:

无锁数据结构的动态内存管理。

此库实现了危害指针内存回收机制,特别是针对C++ 并发技术规范中提出的。它源自 Facebook 的 实现,该实现位于 Facebook 的 Folly 库中。实现的前期阶段都是现场直播的。

在较高层面,危害指针提供了一种机制,允许共享指针的读取者防止在读取操作进行期间,并发写入者对指向的对象进行并发回收。当写入者从数据结构中删除一个对象时,它会指示危害指针库该对象不再可达(即它是退休的),并且库应该在安全时最终丢弃该对象(回收它)。同时,读取者会在任何想要通过共享指针与写入者读取数据时通知库。内部,库会记录读取的地址,以便确保如果指向的对象在读取者仍然可以访问时被退休,它不会被回收。只有当读取者不再可以访问读取指针时,库才允许回收对象。

待办:还可以帮助解决ABA问题(确保对象在没有任何指针指向它之前不被重用,因此不能“看到”A,直到没有A为止)。

关于危害指针的并发垃圾回收概述,请参阅“使用危害指针的无畏并发”。关于使用基于周期的回收(见下文)的替代方法的讨论,Aaron Turon在"无需垃圾回收的锁自由"上的帖子也是一个很好的参考。

高级API结构

待办:参考提案的第3节和folly的文档

危害指针与其他延迟回收机制的比较

待办:参考提案的第3.4节和第4节以及folly文档中的部分

特别注意内存使用

示例

待办:参考提案的第5节和folly文档中的示例

use haphazard::{AtomicPtr, Domain, HazardPointer};

// First, create something that's intended to be concurrently accessed.
let x = AtomicPtr::from(Box::new(42));

// All reads must happen through a hazard pointer, so make one of those:
let mut h = HazardPointer::new();

// We can now use the hazard pointer to read from the pointer without
// worrying about it being deallocated while we read.
let my_x = x.safe_load(&mut h).expect("not null");
assert_eq!(*my_x, 42);

// We can willingly give up the guard to allow writers to reclaim the Box.
h.reset_protection();
// Doing so invalidates the reference we got from .load:
// let _ = *my_x; // won't compile

// Hazard pointers can be re-used across multiple reads.
let my_x = x.safe_load(&mut h).expect("not null");
assert_eq!(*my_x, 42);

// Dropping the hazard pointer releases our guard on the Box.
drop(h);
// And it also invalidates the reference we got from .load:
// let _ = *my_x; // won't compile

// Multiple readers can access a value at once:

let mut h = HazardPointer::new();
let my_x = x.safe_load(&mut h).expect("not null");

let mut h_tmp = HazardPointer::new();
let _ = x.safe_load(&mut h_tmp).expect("not null");
drop(h_tmp);

// Writers can replace the value, but doing so won't reclaim the old Box.
let old = x.swap(Box::new(9001)).expect("not null");

// New readers will see the new value:
let mut h2 = HazardPointer::new();
let my_x2 = x.safe_load(&mut h2).expect("not null");
assert_eq!(*my_x2, 9001);

// And old readers can still access the old value:
assert_eq!(*my_x, 42);

// The writer can retire the value old readers are seeing.
//
// Safety: this value has not been retired before.
unsafe { old.retire() };

// Reads will continue to work fine, as they're guarded by the hazard.
assert_eq!(*my_x, 42);

// Even if the writer actively tries to reclaim retired objects, the hazard makes readers safe.
let n = Domain::global().eager_reclaim();
assert_eq!(n, 0);
assert_eq!(*my_x, 42);

// Only once the last hazard guarding the old value goes away will the value be reclaimed.
drop(h);
let n = Domain::global().eager_reclaim();
assert_eq!(n, 1);

// Remember to also retire the item stored in the AtomicPtr when it's dropped
// (assuming of course that the pointer is not shared elsewhere):
unsafe { x.retire(); }

与规范的不同

与folly的不同

待办:注意与规范和folly的差异。其他方面,请参阅folly的此注释

依赖关系

~0–25MB
~334K 额外代码行数