从question开始,我决定看看是否可以使用(多个)QFile
来实现正确的异步文件I / O.我们的想法是使用在单个文件上运行的QFile
个对象的“池”,并通过QtConcurrent
API分派请求,每个对象都使用专用的QFile
个对象。任务完成后,将发出结果(如果是读取)并且QFile
对象返回到池中。我的初始测试似乎表明这是一种有效的方法,实际上确实允许并发读/写操作(例如,在写入时读取),并且它还可以进一步帮助提高性能(读取可以在写入之间完成)。
显而易见的问题是阅读和编写文件的相同部分。为了看看会发生什么,我使用上面提到的方法来设置情况,让它在文件的同一部分疯狂地写和读。为了发现可能的“腐败”,我在段的开头增加了一个数字,并在写入结束时增加了一个数字。这个想法是,如果读取在开始或结束时读取不同的数字,它可以在实际情况下读取损坏的数据,因为在这种情况下它确实读取了部分写入的数据。
读取和写入重叠很多,所以我知道它们是异步发生的,但不是一次输出“错误”。它基本上意味着读取将永远不会读取部分写入的数据。至少在Windows上。使用QIODevice::Unbuffered
标志并没有改变它。
我假设在操作系统级别上进行某种锁定以防止这种情况(或者可能是缓存?),如果这个假设错误,请纠正我。我基于这样一个事实,即在写入开始之后开始的读取可以在写入完成之前完成。由于我计划在其他平台上部署应用程序,我想知道我是否可以指望Qt支持的所有平台(主要是基于POSIX和Android的平台),或者我需要自己实际实现锁定机制这些情况 - 推迟从正在写入的段读取。
答案 0 :(得分:1)
QFile
的实现中没有任何内容可以保证写入的原子性。因此,使用多个QFile
对象访问同一基础文件的相同部分的想法永远不会正常工作。您在Windows上的测试并不表示没有问题,它们仅仅是不够的:如果它们已经足够,它们就会产生您期望的问题。
对于小的,可能重叠的块的高性能文件访问,您必须:
这是通过首先预取来完成的 - 要么从要访问的字节范围内的每个页面读取,要么丢弃结果,要么使用特定于平台的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());
});
}