我正在编写一个程序,其中多个线程可能同时从一个文件读取。 没有线程正在写入文件,但它们可能会将其内容复制到单独的内存段中。
要做到这一点,我需要使用一个API给我一个文件描述符我想读的文件。 我用C的read
functionread
文件的块。 手册页上说:“成功时返回读取的字节数(零表示文件结束),文件位置由此数字提前”。 但是,我不能find任何明确的信息,但是文件位置的提升是否是线程安全的。
假设我有线程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。 正在读取该文件的线程将根据客户端请求获取该文件描述符。 如果每个线程都存储自己关于文件位置的信息,那么应该没问题。 我只是无法find有关文件位置的任何信息,而read()
指出它是什么。
read
本身是线程安全的,但这并不意味着你想要做的事情是线程安全的。 每个POSIX( 2.9.7与常规文件操作的线程交互 ):
在POSIX.1-2008规定的作用于常规文件或符号链接的效果中,下列所有功能应相互原子化:
…
( read
在下面的列表中。)
除此之外,这意味着读取数据和推进当前文件的位置是相互原子的,读取的每个字节只能被读取一次。 但是,还有其他的考虑可能会使你的工作复杂化,特别是:
短读: read(fd, buf, n)
不需要读取n
个字节。 它可以读取1到n
个字节之间的任何地方,当再次调用它来读取其余部分时,第二次读取与第一次读取不再是原子的。
其他文件类型:POSIX只保证read
常规文件和其他类型的原子性。 像Linux这样的具体系统可能有更强的保证,但我会谨慎。
使用pread
函数(您可以指定一个文件偏移量来读取, 而不必寻找该位置,以及生成的文件位置保持不变),或者在您的文件访问周围执行锁定以避免此类问题。
注意事项:
read()
函数将尝试从与打开的文件描述符fildes
关联的文件中读取nbyte
字节到buf
。 未指定同一管道,FIFO或终端设备上多个并发读取的行为。 (重点是我的)
从相同的参考:
ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);
…
pread()
函数应该等同于read()
,除了它应该从文件中的给定位置读取而不改变文件指针。pread()
的前三个参数与pread()
相同,在文件内部添加了第四个参数偏移量。 试图在无法找到的文件上执行pread()
将导致错误。
每个线程可以跟踪自己的offset
,并指定它。
您对线程安全的理解不正确,或者您误用了它。
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
是线程安全的。