58 次发布
0.5.3 | 2024 年 8 月 16 日 |
---|---|
0.5.2 | 2024 年 6 月 17 日 |
0.5.1 | 2024 年 5 月 30 日 |
0.5.0 | 2024 年 3 月 19 日 |
0.1.2 | 2018 年 10 月 26 日 |
#11 在 缓存 类别中排名
6,320 每月下载量
在 6 个 Crates 中使用 (直接使用 4 个)
1MB
17K SLoC
Concread
为 Rust 开发的并发可读数据结构。
并发可读通常称为写时复制(Copy-On-Write)、多版本并发控制(Multi-Version-Concurrency-Control)。
这些结构允许在事务中进行多个读取操作的同时,单个写操作可以执行。读取者保证读取期间内容保持不变,且读取者不会阻塞写操作。写操作是序列化的,就像互斥锁一样。
此库包含并发可读的 Cell 类型以及 Map/Cache 类型。
何时使用这些?
您可以使用这些来代替 RwLock,并可能观察到并行吞吐量的提高。
最佳用途是代替互斥锁/rwlock,其中读取者存在非平凡的时间。
例如,如果您有一个 RwLock,在获取锁、数据更改或读取后立即释放,这可能不会帮助您。
然而,如果您有一个需要长时间保持读锁的 RwLock,写操作将开始停滞 - 或者相反,写操作将导致读取者阻塞并等待写操作完成。
并发可读避免了这一点,因为读取者永远不会阻塞读取者/写操作,写操作也永远不会阻塞或阻塞读取者。这意味着您通过减少停滞而获得并行吞吐量的提升。
此库还包括并发可读的 BTreeMap、HashMap 和自适应替换缓存。当您的 Cell 中至少有 512 字节的数据时,这些最适用,因为它们只会复制更新所需的内容。
如果您不需要键排序,那么 HashMap 可能是大多数应用程序的最佳选择。
什么是并发可读?
在多线程应用程序中,通常需要在线程之间共享数据。在共享这些数据时,存在多种策略 - 原子操作用于单个整数的读取,互斥锁用于单线程访问,RwLock 用于多个读取者或一个写操作,直到无锁(Lock-Free)允许对队列进行多个读取和写入。
无锁技术虽然有其局限性,因为它基于原子操作。这意味着它每次只能持续更新少量数据。这也意味着你没有事务性操作。虽然这对于队列来说很好,但对于需要操作过程中状态一致性的树或哈希表来说则不是很好。在存在无锁树的地方,它们具有这样一个特性:在更新树的时候,所有其他读者都能立即看到这些变化。你的数据可能会在你察觉之前就改变了。
另一方面,互斥锁和读写锁允许保护更复杂的结构。它们保证了所有读者总是看到相同的数据,并且写者是唯一的写者。但是,它们会导致等待访问它们的线程阻塞。例如,如果读者不会让步,读写锁可能会导致很大的延迟,如果操作系统的优先级偏向于其他进程,可能会造成读写者饥饿。
并发可读结构位于这两个点之间。它们提供多个并发读者,具有事务性操作,同时允许单个写者同时进行。
这是通过在写者修改之前复制内部数据来实现的。这允许读者访问旧数据,而不对其进行修改,并允许写者在提交之前就地更改数据。一旦新数据被存储,旧读者将继续访问他们的旧数据——新读者将看到新数据。
这是一个时间和空间的权衡,使用更多的内存来实现更好的并行行为。
安全性
这个库经过了广泛的测试,并在miri(一个Rust未定义行为检查器)的测试中通过。但是,如果你发现任何问题,请告诉我们,以便我们可以修复它!
使用miri或nightly上的asan进行检查
# Follow the miri readme setup steps
cargo clean && MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-stacked-borrows" cargo miri test
RUSTC_FLAGS="-Z sanitizer=address" cargo test
注意:Miri需要禁用隔离,以便在ARC缓存通道中使用clock monotonic。
单指令多数据(SIMD)
如果你使用的是nightly编译器,ARC支持SIMD。要使用它,你需要编译
RUSTFLAGS="-C target-feature=+avx2,+avx" cargo ... --features=concread/simd_support
贡献
请打开一个问题,pr或通过电子邮件直接联系我(见github)
依赖项
~1.2–8.5MB
~50K SLoC