pread线程安全但需独立缓冲区、确定offset、检查返回值及errno;64位offset需编译宏或pread64;小块随机读用pread,高频连续读宜用mmap。

pread 本身线程安全,但文件偏移和缓冲区共享会破坏它
pread 确实不修改文件描述符的当前偏移(lseek 不影响它),调用之间互不干扰——这是它比 read 更适合多线程的关键。但很多人一上手就错在:用同一个 char* 缓冲区被多个线程同时写入,或者把 offset 算错了(比如基于共享变量递增,没加锁)。结果不是数据错位,就是读到一半被覆盖。
实操建议:
- 每个线程必须使用**独立分配的缓冲区**,别共用全局
buffer或栈上同一块内存 -
offset值要由调用方完全确定,不能依赖lseek(fd, 0, SEEK_CUR)或其他可能被并发干扰的状态 - 如果 offset 来自某个“任务队列”,确保队列分发逻辑是线程安全的(比如用
std::queue+std::mutex,或无锁结构) - 不要在
pread前后对同一 fd 调用lseek、write、truncate等可能改变文件状态的操作——虽然pread不改偏移,但这些操作会影响其他线程对文件长度/内容的预期
pread 返回值和 errno 必须每次都检查,尤其 EINTR 和 EINVAL
Linux 下 pread 可能因信号中断返回 -1 并置 errno = EINTR,也常因传入负 offset 或超出文件大小返回 EINVAL。不检查就直接 memcpy,轻则读到垃圾数据,重则 core dump。
实操建议:
- 永远用
ssize_t n = pread(fd, buf, count, offset);接收返回值,别用int - 检查
n == -1后,立刻判断errno:if (errno == EINTR) continue;(重试),else if (errno == EINVAL)就该打日志并跳过该 offset - 注意
n == 0表示已到 EOF(哪怕 offset 在文件末尾),不是错误;但n > 0才表示成功读取了对应字节数 - 别假设
count一定能读满——即使 offset 合法且文件足够长,内核也可能只返回部分数据(比如磁盘 I/O 暂时受限)
64 位 offset 需要 __off64_t 和 pread64,glibc 版本和编译选项有坑
Linux 默认 pread 的 off_t 是 32 位(在某些旧系统或未定义宏的编译环境下),超过 2GB 的文件 offset 会被截断。直接传一个 uint64_t offset = 0x100000000ULL,进去就变 0,读的全是开头。
实操建议:
- 编译时加
-D_FILE_OFFSET_BITS=64(推荐),或显式用pread64+off64_t - 检查 glibc 版本:
getconf GNU_LIBC_VERSION,低于 2.28 的某些发行版对大文件支持不稳定 - 用
stat或fstat获取文件大小时,也得用struct stat64/stat64()配套,否则st_size可能溢出 - 在代码里加静态断言:
static_assert(sizeof(off_t) >= 8, "off_t must be 64-bit");
和 mmap 对比:pread 适合小块随机读,不适合高频连续扫描
如果你的场景是“每个线程按固定 stride 读 4KB 块”,pread 没问题;但要是“反复读相邻区域、总读量超百 MB”,mmap + 指针偏移通常更快——少了每次系统调用开销和内核/用户空间拷贝。不过 mmap 有自己的一堆麻烦:SIGBUS、页面对齐、MAP_SHARED 写冲突、munmap 时机等。
实操建议:
- 单次
pread读 1ms,优先用pread——简单、可控、无需管理映射生命周期 - 若需反复访问同一文件的多个位置(比如索引查找),考虑
mmap+MAP_POPULATE预加载,但务必处理SIGBUS信号(文件被 truncate 时触发) - 别混用:
mmap映射的区域,不要再用pread去读同一段——行为未定义,尤其在MAP_SHARED下可能读到脏页缓存外的内容
事情说清了就结束。真正难的不是调用 pread,而是让 offset 计算、缓冲区生命周期、错误恢复这三件事在线程间彻底解耦——漏掉任意一环,都可能在高并发下偶发崩掉。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











