fseek在多线程中不可用,因其操作FILE*共享的内部偏移指针,引发竞态;应改用pread,它以显式offset参数实现无锁并行读取。

为什么 fseek 在多线程中不能直接用指针加速偏移定位
因为 fseek 操作的是文件流的共享内部位置指针(FILE* 的 __offset 或类似字段),不是用户可控的独立变量。多个线程对同一 FILE* 调用 fseek 会相互覆盖、竞争,导致定位错乱——这不是“慢”,而是**根本不可用**。
真正能并行定位的,是底层系统调用层面的偏移控制,比如 pread,它把文件描述符、缓冲区、偏移量三者打包成一次原子读,不依赖流状态。
- 别试图用
int*或off_t*去“缓存”或“共享”当前偏移——这解决不了竞态,反而掩盖问题 -
std::ifstream的seekg同样基于fseek,多线程共用一个流对象时行为未定义 - 如果你已经用
open()获取了int fd,就不要再包一层FILE*,否则失去控制权
如何用 pread 实现无锁偏移定位 + 并行读取
pread 是 POSIX 标准函数,接受显式 offset 参数,不改变文件描述符的当前偏移,天然适合多线程按块读取。
关键点在于:每个线程只负责自己那块数据的起始偏移,传给 pread 即可,完全不需要维护或同步“当前偏移”状态。
- 先用
stat()获取文件总大小,划分逻辑块(如每块 64KB) - 每个线程计算自己的
offset = block_id * block_size,作为pread第四个参数 - 确保每个线程有独立的缓冲区(
char* buf),避免写冲突 - 错误检查必须做:
pread返回值可能小于请求长度(到 EOF),也可能是 -1(失败)
mmap
- 映射后拿到的是
void*,强制转为const char*才能做指针算术:const char* base = static_cast<const char>(addr)</const> - 访问
base[offset]前,必须保证offset在映射范围内(offset ),否则触发 SIGBUS - 大文件(>数 GB)映射可能失败或拖慢进程启动;小文件(pread 直接
-
mmap不自动刷新磁盘内容,修改后需msync,纯读场景可忽略
跨平台时怎么处理 Windows 上没有 pread 和 mmap 的问题
Windows 没有 pread,但有等价的 ReadFile + LARGE_INTEGER 偏移参数;也没有标准 mmap,但可用 CreateFileMapping + MapViewOfFile 实现类似语义。
不要手写条件编译宏堆砌逻辑。推荐封装一层轻量 I/O 接口:
- 定义结构体
file_reader,含fd(POSIX)或hFile(Win32)成员 - 提供统一函数
read_at(int64_t offset, void* buf, size_t len),内部按平台分发 - 在 Windows 上用
SetFilePointerEx+ReadFile组合也能做到无锁,但要注意SetFilePointerEx修改句柄位置——所以必须加LOCKFILE_EXCLUSIVE_LOCK或改用ReadFile的lpOverlapped参数指定偏移(需开启异步句柄)
最容易被忽略的一点:Windows 的文件句柄默认不支持重叠 I/O,创建时必须带 FILE_FLAG_OVERLAPPED,否则 lpOverlapped 会被忽略,退化为阻塞行为。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











