#原子 #ipc #threading

atomicslice

A Rust 库,用于线程安全的共享切片,读取速度尽可能快,同时也可写入。

1 个不稳定版本

0.1.0 2024年1月20日

#588 in 并发

MIT 许可证

19KB
296

atomicslice

A Rust 库,用于线程安全的共享切片,读取速度尽可能快,同时也可写入。

概述

使用 AtomicSlice<T> 就像使用 RwLock<[T]> 一样,并知道 .read() 是无等待的。可以通过 Arc<AtomicSlice<T>> 或作为范围线程之间的 &AtomicSlice<T> 传递,并尽可能多地调用 .read().write()。切片在构造时可以是任何长度,但后续写入必须通过相同长度的切片。

AtomicSlice<T> 中读取已优化为无等待并尽可能快。调用 .read() 导致总共三个原子操作,并且永远不会阻塞或以其他方式自旋或等待。另一方面,调用 .write() 可能会导致一些等待。

实现细节

内部,AtomicSlice<T> 存储多个冗余切片的池,其中一个是概念上正在读取的,而其他则可能是被一些 .read() 游离者读取的同时准备写入。这些池中的哪一个用于读取由共享原子整数 status 指示,该整数使用位打包同时编码所有切片的锁定信息。

读取时,通过单个原子操作获取status并增加所有池的使用计数。一旦知道活动切片索引,未使用的其他切片的使用计数将减少。然后,对活动切片进行保护,并可以按需读取。客户端读取后,活动切片的使用计数也会相应减少。read()方法执行前两个操作,并返回一个锁保护对象,其drop()方法执行第三个。

write()方法在AtomicSlice<T>中实际上被互斥锁保护,从而实现了写操作的序列化。一旦获得该锁,.write()方法将在池中从当前活动切片中找到一个单独的切片,并自旋直到其使用计数为零。此时,没有新的或当前的读取将访问未使用的切片,因此.write()方法将提供的数据复制到其中。最后,当前切片的索引被更新,指向新填充的切片,读者将从该切片开始找到新的数据。

目前,使用恰好两个的池大小,这是最低限度的,但似乎工作得很好。将来,我可能进行一些分析,看看权衡是什么。


讨论

  • 是否可以放松一些原子排序,使其小于Ordering::SeqCst
    • 不知道,可能吧?对于大多数情况,获取和释放可能是一条路,我只是目前难以理解它们的精确影响。
  • 为什么不使用arc-swap Crate
    • 因为我最终计划将内部结构作为原始指针和原子操作暴露给基于LLVM的JIT引擎,作为另一个项目的一部分。该项目涉及实时DSP,其中数组需要连续读取和偶尔更新。对无等待代码、关注数组数据以及需要理解原子操作的底层序列的罕见交集使我决定自己编写。此外,这也是一项有趣的练习。
  • 你能不能只通过从AtomicPtr执行单个加载来实现.read()
    • 是的,如果你不介意每次写入时泄漏内存。理论上,你可以通过每次调用.write()时分配和泄漏一个数组来正确地实现这一点,然后使用单个原子指针将所有读取器指向它。为了防止这种灾难性的泄漏,至少需要两个额外的操作来与.read()方法的使用同步。

无运行时依赖