Понимание одновременной записи файлов из нескольких процессов
Отсюда: атомный файл добавляется в UNIX
Рассмотрим случай, когда несколько процессов открывают один и тот же файл и добавляют к нему. O_APPEND гарантирует, что поиск до конца файла и затем начало операции записи является атомарным. Таким образом, несколько процессов могут добавлять к одному и тому же файлу, и ни один процесс не будет перезаписывать запись любых других процессов, поскольку каждый размер записи <= PIPE_BUF.
Я написал тестовую программу, где несколько процессов открываются и пишут в один файл (write(2)
). Я уверен, что каждый размер записи> PIPE_BUF (4 КБ). Я ожидал увидеть случаи, когда процесс перезаписывает чужие данные. Но этого не происходит. Я тестировал с разными размерами записи. Это просто удача или есть причина, по которой этого не происходит? Моя конечная цель - понять, нужно ли нескольким процессам, присоединяющимся к одному и тому же файлу, координировать свои записи.
Вот полная программа. Каждый процесс создает буфер int, заполняет все значения своими rank
, открывает файл и пишет в него.
Спецификации: OpenMPI 1.4.3 на 64-битном Opensuse 11.3
Скомпилировано как: mpicc -O3 test.c, запустится как: mpirun -np 8 ./a.out
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int
main(int argc, char** argv) {
int rank, size, i, bufsize = 134217728, fd, status = 0, bytes_written, tmp_bytes_written;
int* buf;
char* filename = "/tmp/testfile.out";
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
buf = (int*) malloc (bufsize * sizeof(int));
if(buf == NULL) {
status = -1;
perror("Could not malloc");
goto finalize;
}
for(i=0; i<bufsize; i++)
buf[i] = rank;
if(-1 == (fd = open(filename, O_APPEND|O_WRONLY, S_IWUSR))) {
perror("Cant open file");
status = -1;
goto end;
exit(-1);
}
bytes_written = 0;
if(bufsize != (tmp_bytes_written = write(fd, buf, bufsize))) {
perror("Error during write");
printf("ret value: %d\n", tmp_bytes_written);
status = -1;
goto close;
}
close:
if(-1 == close(fd)) {
perror("Error during close");
status = -1;
}
end:
free(buf);
finalize:
MPI_Finalize();
return status;
}
3 ответа
Атомность пишет меньше PIPE_BUF
применяется только к трубам и FIFO. Для записи в файл POSIX говорит:
Этот том POSIX.1-2008 не определяет поведение одновременной записи в файл из нескольких процессов. Приложения должны использовать некоторую форму управления параллелизмом.
... это означает, что вы сами по себе - разные UNIX-лайки дают разные гарантии.
Во-первых, O_APPEND или эквивалентный FILE_APPEND_DATA в Windows означает, что приращения максимального экстента файла ("длины файла") являются атомарными при одновременной записи, и это в любом случае, а не только PIPE_BUF. Это гарантируется POSIX, и Linux, FreeBSD, OS X и Windows все реализуют это правильно. Samba также реализует это правильно, NFS до v5 нет, так как ему не хватает возможности форматирования проводов для атомарного добавления. Таким образом, если вы откроете свой файл только для добавления, одновременные записи не будут разрываться по отношению друг к другу на любой основной ОС, если не задействована NFS.
Это ничего не говорит о том, увидит ли чтение когда-либо порванную запись, и на этом POSIX говорит следующее об атомарности read() и write() для обычных файлов:
Все следующие функции должны быть атомарными по отношению друг к другу в эффектах, указанных в POSIX.1-2008, когда они работают с обычными файлами или символическими ссылками... [много функций] ... read() ... write() ... Если два потока каждый вызывают одну из этих функций, каждый вызов должен видеть все указанные эффекты другого вызова, или ни одного из них. [Источник]
а также
Записи могут быть сериализованы относительно других операций чтения и записи. Если read() файловых данных может быть доказано (любым способом) после write() данных, это должно отражать этот write(), даже если вызовы выполняются разными процессами. [Источник]
но наоборот:
Этот том POSIX.1-2008 не определяет поведение одновременной записи в файл из нескольких процессов. Приложения должны использовать некоторую форму управления параллелизмом. [Источник]
Безопасное толкование всех трех из этих требований предполагает, что все записи, перекрывающие экстент в одном и том же файле, должны быть сериализованы относительно друг друга, и такие операции чтения, чтобы разорванные записи никогда не показывались читателям.
Менее безопасная, но все же разрешенная интерпретация может заключаться в том, что чтение и запись сериализуются только друг с другом между потоками внутри одного и того же процесса, а записи между процессами сериализуются только для чтения (т. Е. Между потоками последовательно последовательное упорядочение ввода-вывода). процесс, но между процессами I / O является только приобретением-выпуском).
Конечно, только потому, что стандарт требует этой семантики, не означает, что реализации соответствуют, хотя на самом деле FreeBSD с ZFS работает отлично, совсем недавно Windows (10.0.14393) с NTFS работает отлично, а последние Linux с ext4 ведут себя правильно, если включен O_DIRECT, Если вы хотите получить более подробную информацию о том, насколько основные ОС и файловые системы соответствуют стандарту, см. Этот ответ.
Это не удача, в том смысле, что если вы покопаетесь в ядре, вы, вероятно, сможете доказать, что в ваших конкретных обстоятельствах никогда не случится, что один процесс " write
чередуется с другим. Я предполагаю, что:
- Вы не устанавливаете ограничения на размер файла
- Вы не заполняете файловую систему, в которой вы создаете тестовый файл
- Файл представляет собой обычный файл (не сокет, канал или что-то еще)
- Файловая система является локальной
- Буфер не охватывает несколько отображений виртуальной памяти (известно, что это правда, потому что это
malloc()
Ed, который помещает его в кучу, которая является смежной. - Процессы не прерываются, не сигнализируются и не отслеживаются во время
write()
занят. - Нет ошибок дискового ввода-вывода, сбоев оперативной памяти или любых других ненормальных условий.
- (Может быть, другие)
Вы, вероятно, действительно обнаружите, что если все эти предположения верны, то это тот случай, когда ядро операционной системы, которое вы используете, всегда выполняет один write()
Системный вызов с одной атомарной непрерывной записью в следующий файл.
Это не значит, что вы можете рассчитывать на то, что это всегда правда. Вы никогда не знаете, когда это может быть неправдой, когда:
- программа работает в другой операционной системе
- файл перемещается в файловую систему NFS
- процесс получает сигнал в то время как
write()
в процессе иwrite()
возвращает частичный результат (меньше байтов, чем запрошено). Не уверен, что POSIX действительно позволяет этому случиться, но я программирую с защитой! - так далее...
Таким образом, ваш эксперимент не может доказать, что вы можете это сделать с записями без чередования.