文章目录
  1. 1. 问题现象
  2. 2. 问题分析
  3. 3. 解决方案
  4. 4. Tips:
  5. 5. 参考链接:

问题现象

这几天遇到一个问题,用相同的索引(offset,length)从同一个文件fd中读取一段数据,通过CRC校验值发现偶尔有读错的情况:读到的期望CRC校验值与根据所读到数据算出来的校验值有出入,从读到数据来看,有时刚好错位了4个Bytes。

问题分析

分析日志发现竟然有多个线程并发读同一个文件fd的操作,之前设计读时没有考虑支持并发读的场景,因此也没有加锁互斥,但是Redo时的日志读与业务异步刷盘队列满之后不断重试的日志读产生了并发干扰。

根据索引到日志文件中读日志实际上有两个步骤:(1) seek到offset位置;(2) read指定长度的数据。这两个步骤无法保证原子性。上面的问题可以肯定是由于多线程seek产生了干扰导致读到非预期的数据。

解决方案

所以seek/lseek + read/write 在多个线程并发读写同一个fd会有有问题,最容易想到的解决方法是在seek/lseek+read/write的外层加锁互斥访问,这是一个方法;其实系统库提供了更简洁的方法——pread/pwrite系统调用,多线程环境下尤其好使,这个APUE里面有介绍(几年前看过,用得太少都不太记得了…Orz)
pread/pwrite系统调用将seek/lseek + read/write 两个步骤打包为原子操作, 读写时文件的offset不会改变,因而多个线程读写时不会产生干扰。
同样,多线程对同一个文件fd的并发写也可以通过pwrite来搞定。不过我们的算法日志为追加写,没有这种场景。

Tips:

1
2
3
4
5
6
7
8
9
#define _XOPEN_SOURCE 500 /*该宏需要在头文件之前定义,否则编译会有告警*/
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

Notes:
The pread() and pwrite() system calls are especially useful in multithreaded applications.
They allow multiple threads to perform I/O on the same file descriptor without being
affected by changes to the file offset by other threads.

参考链接:

  1. pread man7
  2. pread and pwrite not defined?
文章目录
  1. 1. 问题现象
  2. 2. 问题分析
  3. 3. 解决方案
  4. 4. Tips:
  5. 5. 参考链接: