QFile: несколько дескрипторов одного и того же физического файла не записывают все данные

Мне было интересно, как ведет себя QFile, когда несколько дескрипторов открываются для одного и того же файла (с использованием C++ в Visual Studio 2013 в Windows 7), поэтому я написал следующую небольшую программу:

  QFile file("tmp.txt");
  file.open(QIODevice::WriteOnly | QIODevice::Truncate);
  QTextStream ts(&file);
  ts << "Hallo\n";

  QFile file2("tmp.txt");
  file2.open(QIODevice::WriteOnly | QIODevice::Append);
  QTextStream ts2(&file2);
  ts2 << "Hallo 2\n";

  file.close();

  ts2 << "Hello again\n";

  file2.close();.

Это приводит к следующему выводу в файле tmp.txt:

Hallo 2
Hello again

Итак, первый оператор Hallo потерялся. Если я делаю ts.flush() сразу после ts << "Hallo\n", этого не происходит, что заставляет меня думать, что оператор потерялся во внутренних буферах QString или был перезаписан последующими операторами вывода. Однако я хочу использовать QFile в структуре ведения журнала, поэтому я не хочу всегда сбрасывать, так как это снизит производительность.

Я также пробовал то же самое с std::basic_ostream<char> вместо QFile:

  std::basic_ofstream<char> file;
  file.open("tmp.txt", std::ios_base::out | std::ios_base::ate | std::ios_base::app);
  file << "Hallo\n";

  std::basic_ofstream<char> file2;
  file2.open("tmp.txt", std::ios_base::out | std::ios_base::ate | std::ios_base::app);
  file2 << "Hallo 2\n";

  file.close();

  file2 << "Hello again\n";

  file2.close();

который выводит, как я и ожидал:

Hallo
Hallo 2
Hello again

Так в чем проблема с примером QFile? Разве QFile не предназначено для использования с несколькими дескрипторами, указывающими на один и тот же файл, или что именно здесь происходит? Я думал, что мой вариант использования довольно распространен, поэтому я немного удивлен, обнаружив такое поведение. Я не смог найти больше подробностей в документации по Qt. Я прочитал здесь, что Qt открывает файл в общем режиме, так что это не должно быть проблемой.

В конце концов я хочу использовать QFile для ведения журнала (где доступ к функции, которая выполняет фактическую запись, конечно же, синхронизирован), но этот небольшой пример беспокоит меня тем, что некоторые операторы журнала могут быть потеряны по пути. Как вы думаете, было бы лучше использовать потоки STL вместо QFile?

Изменить Как было указано, std::endl вызывает сброс, поэтому я изменил приведенный выше пример STL, чтобы использовать только \n, который согласно здесь сброса не вызывает. Однако поведение, описанное выше, не изменилось.


person kafman    schedule 15.02.2016    source источник
comment
станд::эндл; тоже использовал промывку   -  person AchmadJP    schedule 15.02.2016
comment
Только что проверил без std::endl - такое же поведение (сообщение отредактировано)   -  person kafman    schedule 15.02.2016


Ответы (3)


Мне кажется, ты хочешь и того, и другого.

Если вам нужно несколько буферов записи и не хотите их очищать, трудно быть уверенным, что все операции записи находятся в файле и в правильном порядке. Ваш небольшой тест с std::basic_ostream не является доказательством: будет ли он работать с большими записями? Будет ли работать на других ОС? Вы хотите рискнуть своим процессом ради (пока не доказанного) увеличения скорости?

person Ilya    schedule 15.02.2016

Происходит несколько подозрительных вещей. Для начала вы вводите два уровня буферизации.

  1. Прежде всего, QTextStream имеет внутренний буфер, который вы можете очистить, вызвав для него flush.

  2. Во-вторых, QFile также выполняет буферизацию (или, лучше, использует буферизованные API из вашей библиотеки -- fopen, fwrite и т. д.). Передайте QIODevice::Unbuffered в open, чтобы он использовал небуферизованные API (open , write, ...).

Теперь, поскольку это ужасно подвержено ошибкам, QTextStream::flush фактически сбрасывает и базовое файловое устройство.

Кроме того, вы передаете WriteOnly | Append, что не имеет смысла. Это только один из двух.


Однако обратите внимание, что ваши записи могут по-прежнему чередоваться. POSIX.1-2013 говорит, что

Запись является атомарной, если весь объем, записанный за одну операцию, не чередуется с данными из какого-либо другого процесса. Это полезно, когда несколько модулей записи отправляют данные на один модуль чтения. Приложения должны знать, насколько большой запрос на запись может быть выполнен атомарно. Этот максимум называется {PIPE_BUF}.

(В Windows понятия не имею).

person peppe    schedule 15.02.2016
comment
Что касается двух уровней буферизации: то, что вы говорите, имеет смысл, но использование QTextStreams на самом деле так же, как это делается в документации Qt... doc.qt.io/qt-5/qfile.html. FWIW, у меня такое же поведение, если я не использую поток, а напрямую file.write(). Что касается флагов: вы правы, я изменил его, чтобы использовать только QIODevice::Append, и добавил QIODevice::Unbuffered, но это не изменило результат. - person kafman; 15.02.2016
comment
Поэтому, если я использую QIODevice::Unbuffered и напрямую работаю с файлом с помощью file.write(), он работает, как вы предложили. Однако, как указано в моем исходном посте, я хотел бы выяснить, что не используется сброс (или, в этом примере, используются буферы), чтобы повысить производительность. Так что STL просто лучше? - person kafman; 15.02.2016

 QFile file("tmp.txt");
  file.open(QIODevice::WriteOnly | QIODevice::Truncate);
  QTextStream ts(&file);
  ts << "Hallo\n";

  QFile file2("tmp.txt");
  file2.open(QIODevice::Append);
  QTextStream ts2(&file2);
  ts2 << "Hallo 2\n";

  file.close();

  ts2 << "Hello again\n";

  file2.close();

Попробуйте это, я изменил его, чтобы Truncate не вызывался WriteOnly. Глупый я не читал это. {ОБНОВИТЬ}

QIODevice::WriteOnly    0x0002  The device is open for writing. Note that this mode implies Truncate.
QIODevice::Truncate 0x0008  If possible, the device is truncated before it is opened. All earlier contents of the device are lost.

Источник чтения: http://doc.qt.io/qt-5/qiodevice.html#OpenModeFlag-enum

person AchmadJP    schedule 15.02.2016
comment
Такое же поведение - почему порядок аргументов для двоичного файла или имеет значение? - person kafman; 15.02.2016
comment
Извините, смотрите мое обновление. Можешь попробовать? ReadWrite вызывает Write, которые вызывают Truncate. - person AchmadJP; 15.02.2016
comment
Найдите его здесь: doc.qt.io/qt-5/qiodevice. .html#OpenModeFlag-enum Ну вот. - person AchmadJP; 15.02.2016
comment
Хорошо, я изменил его на file.open(QIODevice::WriteOnly) и file2.open(QIODevice::Append), но поведение осталось прежним. - person kafman; 15.02.2016