Qt平台上的操作系统级文件I / O锁

时间:2016-09-01 12:25:38

标签: c++ multithreading qt

question开始,我决定看看是否可以使用(多个)QFile来实现正确的异步文件I / O.我们的想法是使用在单个文件上运行的QFile个对象的“池”,并通过QtConcurrent API分派请求,每个对象都使用专用的QFile个对象。任务完成后,将发出结果(如果是读取)并且QFile对象返回到池中。我的初始测试似乎表明这是一种有效的方法,实际上确实允许并发读/写操作(例如,在写入时读取),并且它还可以进一步帮助提高性能(读取可以在写入之间完成)。

显而易见的问题是阅读和编写文件的相同部分。为了看看会发生什么,我使用上面提到的方法来设置情况,让它在文件的同一部分疯狂地写和读。为了发现可能的“腐败”,我在段的开头增加了一个数字,并在写入结束时增加了一个数字。这个想法是,如果读取在开始或结束时读取不同的数字,它可以在实际情况下读取损坏的数据,因为在这种情况下它确实读取了部分写入的数据。

读取和写入重叠很多,所以我知道它们是异步发生的,但不是一次输出“错误”。它基本上意味着读取将永远不会读取部分写入的数据。至少在Windows上。使用QIODevice::Unbuffered标志并没有改变它。

我假设在操作系统级别上进行某种锁定以防止这种情况(或者可能是缓存?),如果这个假设错误,请纠正我。我基于这样一个事实,即在写入开始之后开始的读取可以在写入完成之前完成。由于我计划在其他平台上部署应用程序,我想知道我是否可以指望Qt支持的所有平台(主要是基于POSIX和Android的平台),或者我需要自己实际实现锁定机制这些情况 - 推迟从正在写入的段读取。

1 个答案:

答案 0 :(得分:1)

QFile的实现中没有任何内容可以保证写入的原子性。因此,使用多个QFile对象访问同一基础文件的相同部分的想法永远不会正常工作。您在Windows上的测试并不表示没有问题,它们仅仅是不够的:如果它们已经足够,它们就会产生您期望的问题。

对于小的,可能重叠的块的高性能文件访问,您必须:

  1. 将文件映射到内存。
  2. 序列化对内存的访问,可能使用多个互斥锁来提高并发性。
  3. 同时访问内存,并在数据被分页时不保留互斥锁。
  4. 这是通过首先预取来完成的 - 要么从要访问的字节范围内的每个页面读取,要么丢弃结果,要么使用特定于平台的API。然后锁定互斥锁并将数据从文件中复制出来或复制到其中。 操作系统完成其余的工作。

    class FileAccess : public QObject {
      Q_OBJECT
      QFile m_file;
      QMutex m_mutex;
      uchar * m_area = nullptr;
      void prefetch(qint64 pos, qint64 size);
    public:
      FileAccess(const QString & name) : m_file{name} {}
      bool open() {
        if (m_file.open(QIODevice::ReadWrite)) {
          m_area = m_file.map(0, m_file.size());
          if (! m_area) m_file.close();
        }
        return m_area != nullptr;
      }
      void readReq(qint64 pos, qint64 size);
      Q_SIGNAL readInd(const QByteArray & data, qint64 pos);
      void write(const QByteArray & data, qint64 pos);
    };
    
    void FileAccess:prefetch(qint64 pos, qint64 size) {
      const qint64 pageSize = 4096;
      const qint64 pageMask = ~pageSize;
      for (qint64 offset = pos & pageMask; offset < size; offset += pageSize) {
        volatile uchar * p = m_area+offset;
        (void)(*p);
      }
    }
    
    void FileAccess:readReq(qint64 pos, qint64 size) {
      QtConcurrent::run([=]{
        QByteArray result{size, Qt::Uninitialized};
        prefetch(pos, size);
        QMutexLocker lock{&m_mutex};
        memcpy(result.data(), m_area+pos, result.size());
        lock.unlock();
        emit readInd(result, pos);
      });
    }
    
    void FileAccess::write(const QByteArray & data, qint64 pos) {
      QtConcurrent::run([=]{
        prefetch(pos, data.size());
        QMutexLocker lock{&m_mutex};
        memcpy(m_area+pos, data.constData(), data.size());
      });
    }