异步文件IO有意义吗?

9

像tokio这样的Rust异步运行时提供了许多标准函数的“异步化”副本,包括一些文件IO函数,其原理基本上是通过召唤相应的阻塞任务(在新线程上?)来工作。这些函数的例子包括tokio::fs::create_dir_alltokio::fs::read_dirtokio::fs::read等。

所有这些函数的优点是什么?在异步上下文中,为什么我应该更喜欢使用它们而不是标准的阻塞函数?如果我正在await它们的结果,是否有任何收益?

一个例子是一个基于查询返回某个文件内容的异步Web路由(使用Rocket):

#[get("/foo/<query>")]
async fn foo(query: &str) -> Option<String> {
    let file_path = // interpret `query` somehow and find the target file
    tokio::fs::read_to_string(file_path).await.ok()
    // ^ Why not just `std::fs::read_to_string(file_path).ok()`?
}

我理解在Socket IO或者延时任务(with thread sleep)中使用async/.await的好处,但是在这种情况下似乎对我来说没有意义。相反的,这使得更加复杂的任务在代码中更难解决(例如,在目录列表中搜索文件时处理流)。


6
是的,因为如果有其他并发任务,.await 允许它们在执行文件IO的另一个线程被阻塞时继续运行。在异步任务中使用阻塞调用会阻塞该线程,从而阻塞了在同一线程上运行的所有任务。 - Herohtar
11
“这是绝对错误的说法,因为文件IO任务通常并非总能即刻返回一些字节。虽然数据可能会立即可用(特别是如果该文件最近已被读取并仍在操作系统缓存中),但大多数文件读取将需要一些可观的时间,而这段时间可以更好地用于做其他事情而不是等待。请考虑硬盘启动时间、网络驱动器、虚拟文件系统和大型读取的时间。” - kmdreko
4
如果你简单地使用epoll()或类似的方法查询文件描述符,这个说法才是正确的,即文件描述符“准备好”并不意味着从中读取数据不会因为需要从磁盘(或网络挂载的文件系统中)读取内容而被阻塞,这可能需要很长时间甚至永远无法完成。通过异步版本的std::fs操作,可以将阻塞操作转移到单独的线程中,并安排在整个操作完成时进行唤醒,从而解决了这个问题。 - user4815162342
1
老实说,对于一个文件服务器,在大部分时间里只是在移动字节,可能并不重要。 - kmdreko
1
在Linux上,从磁盘文件读取始终会阻塞,并且使用select/poll接口时始终报告IO已准备好,这是大多数异步库所做的。因此,除非库使用aio接口或来自另一个线程,否则异步不会真正帮助进行磁盘文件IO。 - Colonel Thirty Two
显示剩余4条评论
2个回答

16
tokio::fs::read_to_stringstd::fs::read_to_string的区别在于,Tokio函数会将文件IO调用转移到spawn_blocking线程池,而std::fs::read_to_string不会这样做。
这很重要,因为如果不将文件IO转移到单独的线程中,则会阻塞运行时,这意味着运行时中的其他任务在文件IO操作期间将无法执行。请参见链接以获得更详细的解释。

3

我猜你正在使用一个相当快的驱动器读取本地文件系统上的小文件。如果是这种情况,那么使用这些函数的async版本可能没有太大的意义。

如果你一半的HTTP请求需要从文件系统中读取,那么你可能会注意到有很长时间在等待阻塞IO的运行时。这实际上取决于你的应用程序的本质。也许你只有一个线程?也许你有很多个线程?

但是,在某些边缘情况下,文件系统可能会变得足够慢,成为一个真正的大问题。以下是两个极端案例:

  • 网络挂载的文件系统(例如:NFS、ipfs)。在create_dir_all下可能会有多次网络往返。在这个过程中,您的服务基本上是无响应的。
  • 慢硬盘。旋转磁盘。甚至从CD-ROM驱动器中读取。当一些底层库进行阻塞IO时,虽然你不会从CD-ROM上运行你的Web服务器,但比较两个磁带(是的,物理磁带仍然用于备份)是否相同的工具会受到极大的影响。

现在,如果你正在编写一个公开 async API 的库,你不能对底层文件系统或其支持硬件做出任何假设,并且应该使用非阻塞 IO。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接