我正在编写一个程序,其中多个线程可能同时从文件中读取。没有线程写入文件,但它们可能会将其内容复制到单独的内存段。
要实现这一点,我需要使用一个API,它为我想要读取的文件提供了一个文件描述符。我正在使用C的read
函数读取文件的块。手册页说:“成功时,返回读取的字节数(零表示文件结束),文件位置按此编号提前。”但是,我找不到任何确定的信息,关于文件位置的提升是否是线程安全的。
假设我有线程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
...
如果有帮助,每个线程将使用相同的文件描述符。换句话说,它是使用open()
打开文件的API。正在读取文件的线程然后根据客户端请求获取该文件描述符。如果每个线程都存储自己关于文件位置的信息,那么应该没问题。我找不到任何有关文件位置的信息,read()
在哪里找出它是什么。
答案 0 :(得分:4)
read
本身是线程安全的,但这并不一定意味着你想用它做的事情是线程安全的。每POSIX(2.9.7 Thread Interactions with Regular File Operations):
当对常规文件或符号链接进行操作时,以下所有函数在POSIX.1-2008中指定的效果中应相互原子化:
...
(read
位于随后的列表中。)
除此之外,这意味着读取数据和推进当前文件位置相对于彼此是原子的,并且读取的每个字节将被准确读取一次。但是,还有其他一些因素会让您感到困惑,尤其是:
短读取:read(fd, buf, n)
无需读取n
个字节。它可以读取1到n
字节之间的任何值,当你再次调用它来读取余数时,第二次读取不再是第一次读取的原子。
其他文件类型:POSIX仅保证常规文件的read
的原子性以及其他一些类型。像Linux 这样的特定系统可能有更强的保证,但我会谨慎。
最好使用pread
函数(您可以在其中指定文件偏移量,以便从中读取,而不得不寻找该位置,并且结果文件位置保持不变)或对文件访问执行锁定以避免此类问题。
答案 1 :(得分:0)
注意read:
read()
函数将尝试从与开放文件描述符nbyte
关联的文件中读取fildes
个字节到buf
指向的缓冲区。 未指定同一管道,FIFO或终端设备上的多个并发读取的行为。 (强调我的)
来自同一参考:
ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);
...
pread()
函数应等效于read()
,除了它应从文件中的给定位置读取而不更改文件指针。pread()
的前三个参数与read()
相同,并为文件中的所需位置添加了第四个参数偏移量。尝试对无法搜索的文件执行pread()
将导致错误。
每个帖子都可以跟踪自己的offset
,并指定它。
答案 2 :(得分:0)
您对线程安全的理解不正确或者您误用了它。
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
是线程安全的,如果调用它的两个线程也从不处理相同的数据两次。