13个版本

使用旧的Rust 2015

0.4.2 2018年2月28日
0.3.4 2017年11月29日
0.3.0 2017年5月12日
0.2.2 2017年3月31日
0.1.1 2016年12月18日

#399 in 并发


cernan 中使用

MIT 许可证

61KB
1K SLoC

hopper - 无界mpsc且内存限制

Build Status Codecov Crates.io

Hopper提供了一个版本的Rust标准mpsc,它是无界的但消耗的内存量有限。这是通过在需要时将元素分页到磁盘来实现的。这里的雄心是支持mpsc风格的通信,而不分配无界的内存或丢弃输入。

快速入门

在Cargo.toml中包含hopper库

hopper= "0.4"

并像使用stdlib的mpsc一样使用它

extern crate tempdir;
extern crate hopper;

let dir = tempdir::TempDir::new("hopper").unwrap();
let (mut snd, mut rcv) = hopper::channel("example", dir.path()).unwrap();

snd.send(9);
assert_eq!(Some(9), rcv.iter().next());

这里的主要区别是您必须提供一个通道名称和hopper可以分页到磁盘的目录。

Hopper旨在用于那些您的系统不能卸载输入并且必须最终处理它们的场景。当内存缓冲区满时,Hopper会在磁盘溢出时维护一个输入的内存缓冲区。虽然hopper会分页到磁盘,但它不会像stdlib mpsc那样在重启时保留写入。

幕后细节

Hopper的通道看起来非常像Unix中的命名管道。您为channel_2channel_with_max_bytes_3提供名称,并在其中推入和拉出字节。磁盘分页增加了一个复杂性。在私下里,提供给上述两个函数的名称用于在data_dir下创建一个目录。在这种情况下,该目录会填充单调递增的文件。从现在开始,我们将专门处理这个问题。

磁盘结构如下

data-dir/
sink-name0/
      0
      1
   sink-name1/
      0

您会注意到在这个模块的命名空间中导出了Sender和Receiver。这些是支持命名通道发送和接收端的结构。Sender(可能有多个)负责创建“队列文件”。在上面的示例中,data-dir/sink-name*/*是队列文件。这些文件由Sender作为追加日志处理。Receiver会遍历这些日志以读取其中序列化的数据。

这不会填满我的磁盘吗?

也许吧!每个发送者都有一个关于它可能读取的最大字节数的概念,这个值在创建通道时可以显式设置,使用channel_with_max_bytes,一旦发送者超过这个限制,它会尝试将队列文件标记为只读并创建一个新文件。接收者被编程为读取当前的队列文件,直到遇到文件结束标记EOF并发现文件为只读,此时它会删除文件——它是唯一的读取者——然后继续处理下一个。

hopper::channel_with_max_bytes接受一个max_disk_files参数,定义了可以同时存在的溢出文件的总数。如果所有内存和磁盘缓冲区都已满,则队列中的发送将失败。错误结果被写入,以便调用者可以恢复对输入值的所有权。默认情况下,max_disk_files == usize::max_value(),因此如果接收者无法跟上发送者的速度,那么,糟糕,你的磁盘会逐渐被填满。

我需要哪些类型的文件系统选项?

Hopper旨在在任何奇特的旧文件系统上以任何选项运行,即使在高并发下也能如此。由于常见的文件系统不支持交错小原子写入,hopper一次限制为一个独占发送者和一个独占接收者。这可能会限制mpsc的并发性,但可以保持数据完整性。我们对此领域的改进持开放态度。

hopper的性能如何?

Hopper附带基准测试。我们观察到,性能仅比stdlib的mpsc慢30%,最高可达70倍,具体取决于配置选项、磁盘I/O速度等因素。我们强烈建议您在自己的系统上进行基准测试。

依赖项

~0.7–1MB
~21K SLoC