#algorithm #check #lock #thread #data #data-structures #helps

peace-lock

一个零成本的抽象锁,帮助检查无争用算法

4 个版本

0.1.3 2023 年 9 月 28 日
0.1.2 2023 年 2 月 24 日
0.1.1 2023 年 2 月 24 日
0.1.0 2023 年 2 月 24 日

195并发 中排名

Download history 1/week @ 2024-04-04 1/week @ 2024-04-18 3/week @ 2024-04-25 10/week @ 2024-05-16 78/week @ 2024-05-23 61/week @ 2024-05-30 56/week @ 2024-06-06 58/week @ 2024-06-13 56/week @ 2024-06-20 30/week @ 2024-06-27 46/week @ 2024-07-04 47/week @ 2024-07-11 63/week @ 2024-07-18

每月 206 次下载

MIT 许可证

18KB
378

peace_lock

CI Badge Latest Version docs.rs Badge

一个 Mutex/RwLock,如果存在争用则会导致 panic!

peace_lock 帮助您检查并发算法,在禁用检查模式的情况下成为零成本。

动机

一个期望没有争用的锁似乎不合直觉:一开始使用锁的原因是正确管理争用,对吗?是的,也不对。

有时你会在你的数据结构上实现一个无争用算法。你认为并发写入数据结构是可以的,但编译器对此不满意。

为了调试目的,程序应该在检测到任何争用时大声喊叫,这表明存在错误。这使得你在开发期间选择 RwLock 进行健全性检查,并牺牲了一些性能。

后来,你使用 UnsafeCellunsafe 重新编写算法以使其性能更好。

我们能避免重写和不安全的代码吗?

这里有一个具体的例子

你想要一个允许你并发更改值内容的哈希表。你可以这样做,因为你有一个调度算法确保在任何时候都不会有两个线程同时读取和写入同一个值。

let map: HashMap<usize, usize> = ...;
let shared_map = Arc::new(map);

// do these two concurrently in different threads
thread::spawn(|| { *shared_map.get_mut(&1).unwrap() = 3; });
thread::spawn(|| { *shared_map.get_mut(&2).unwrap() = 4; });

上面的代码不会工作,因为你无法在 Arc 内获取可变引用。但“嘿,编译器,这是安全的,我保证!”因为你那出色的调度算法。

为了使编译器容易,你使用了 RwLock

let map: HashMap<usize, RwLock<usize>> = ...;
let shared_map = Arc::new(map);

// do these two concurrently in different threads
thread::spawn(|| { *shared_map.get(&1).unwrap().write() = 3; });
thread::spawn(|| { *shared_map.get(&2).unwrap().write() = 4; });

由于你知道没有冲突,使用 RwLock 不必要地损害了性能。

后来你决定为了性能做一些黑魔法

let map: HashMap<usize, UnsafeCell<usize>> = ...;
let shared_map = Arc::new(map);

// do these two concurrently in different threads
thread::spawn(|| { unsafe { *shared_map.get(&1).unwrap().get() = 3; } });
thread::spawn(|| { unsafe { *shared_map.get(&2).unwrap().get() = 4; } });

代码运行正确,直到调度器产生了争用。

现在你 wonder:我可以将代码重置为 RwLock 以进行健全性检查,但需要重写太多代码。有没有更简单的方法在我之间切换性能模式和健全性检查模式?

这个 crate 就是为此而设计的。

用法

peace_lock 是 std/parking_lot 的 MutexRwLock 的直接替代品。

peace_lock = "0.1" 添加到您的 Cargo.toml 文件中。在调试构建中,检查模式被启用,如果调用 writeread 时发生竞争,将直接引发 panic。这表明调度算法存在错误!

use peace_lock::{RwLock, Mutex};

let map: HashMap<usize, RwLock<usize>> = ...;
let shared_map = Arc::new(map);

// do these two concurrently in different threads
// The `write` call will panic if there's another thread writing to the value
// simultaneously.
// The panic behaviour can be disabled by feature flags so that `write` becomes
// zero-cost.
thread::spawn(|| { *shared_map.get(&1).unwrap().write() = 3; });
thread::spawn(|| { *shared_map.get(&2).unwrap().write() = 4; });

// and concurrent read is fine
thread::spawn(|| { shared_map.get(&2).unwrap().read(); });
thread::spawn(|| { shared_map.get(&2).unwrap().read(); });

// also mutex
let map: HashMap<usize, Mutex<usize>> = ...;
let shared_map = Arc::new(map);

thread::spawn(|| { *shared_map.get(&1).unwrap().lock() = 3; });
thread::spawn(|| { *shared_map.get(&2).unwrap().lock() = 4; });

在发布构建中,默认情况下省略了检查。这将使锁无成本。您可以通过将 check 功能添加到功能列表中(例如 peace_lock = { version = "0.1", features = ["check"] })显式地打开检查模式。

可选功能

  • owning_ref:将 StableAddress 实现添加到锁卫中。
  • serde:将 SerializeDeserialize 添加到锁中。

需要帮助

如果有人能帮我检查原子排序是否正确使用且不是过于严格,那将非常有帮助。

依赖项

~195KB