C read() 线程安全吗?

Is C read() Thread Safe?

我正在编写一个程序,其中多个线程可能同时从一个文件中读取。没有线程正在写入文件,但它们可能各自将其内容复制到单独的内存段。

要执行此操作,我需要使用 API 为我要读取的文件提供文件描述符。我正在使用 C 的 read 函数读取文件块。手册页说,"On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number." 不过,关于文件位置的推进是否线程安全,我找不到任何确定的信息。

假设我有线程 T1 和线程 T2 一次读取文件的 1 个字节。如果 read() 是线程安全的,我会期望以下内容:

T1: read() => file position == 1
T2: read() => file position == 1
T1: read() => file position == 2
T2: read() => file position == 2
...

但我担心如果它不是线程安全的,那么可能会发生以下情况:

T1: read() => file position == 1
T2: read() => file position == 2
T1: read() => file position == 3
T2: read() => file position == 4
...

如果有帮助,每个线程将使用相同的文件描述符。换句话说,是 API 使用 open() 打开文件。正在读取文件的线程然后根据客户端请求获取该文件描述符。如果每个线程都存储自己的文件位置信息,那么应该没问题。我只是找不到任何关于保存文件位置的信息,以及 read() 找出它的位置。

备注read:

The read() function shall attempt to read nbyte bytes from the file associated with the open file descriptor, fildes, into the buffer pointed to by buf. The behavior of multiple concurrent reads on the same pipe, FIFO, or terminal device is unspecified. (emphasis mine)

来自同一参考文献:

ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);

...

The pread() function shall be equivalent to read(), except that it shall read from a given position in the file without changing the file pointer. The first three arguments to pread() are the same as read() with the addition of a fourth argument offset for the desired position inside the file. An attempt to perform a pread() on a file that is incapable of seeking shall result in an error.

每个线程都可以跟踪自己的offset,并指定它。

read本身是线程安全的,但这并不一定意味着你想用它做的事情是线程安全的。每 POSIX (2.9.7 Thread Interactions with Regular File Operations):

All of the following functions shall be atomic with respect to each other in the effects specified in POSIX.1-2008 when they operate on regular files or symbolic links:

...

read 在后面的列表中。)

除其他外,这意味着读取数据和推进当前文件位置是彼此原子的,并且读取的每个字节将被恰好读取一次。但是,还有其他考虑因素可能会使事情复杂化,尤其是:

  • 短读:read(fd, buf, n)不需要读n字节。它可以读取 1 到 n 字节之间的任何位置,并且当您再次调用它来读取剩余部分时,第二次读取相对于第一次读取不再是原子的。

  • 其他文件类型:POSIX 仅保证 read 常规文件和其他一些类型的原子性。 Linux可能这样的特定系统有更强的保证,但我会谨慎。

最好使用 pread 函数(您可以在其中指定要从 读取的文件偏移量,而无需 查找该位置,并且结果文件位置保持不变)或对文件访问进行锁定以避免此类问题。

您对线程安全的理解不正确或应用不当。

T1: read() => file position == 1
T2: read() => file position == 1
T1: read() => file position == 2
T2: read() => file position == 2
...

在这里,我们让 T1 和 T2 同时调用 read,无论 read 操作以何种顺序发生,我们得到的结果都不可能发生。这是当函数 不是 线程安全时发生的竞争的典型示例。

粗略地说,如果并发调用一个函数会产生理智的结果,那么它就是线程安全的,与非并发调用它时得到的结果相同。如果调用 read 的单个线程从不处理相同数据两次,那么如果调用它的两个线程也从不处理相同数据两次,则 read 是线程安全的。