Что происходит, если системный вызов write вызывается для одного файла двумя разными процессами одновременно
ОС справляется с этим правильно?
Или мне придется вызывать flock()?
5 ответов
Хотя операционная система не будет аварийно завершена и файловая система не будет повреждена, write()
НЕ гарантируется атомарность, если только дескриптор файла не является каналом, а объем записываемых данных PIPE_MAX
байт или меньше. Соответствующая часть стандарта:
Попытка записи в канал или FIFO имеет несколько основных характеристик:
- Атомарный / неатомарный: запись является атомарной, если все количество, записанное в одной операции, не чередуется с данными из любого другого процесса. Это полезно, когда несколько писателей отправляют данные одному читателю. Приложения должны знать, насколько большой запрос на запись может быть выполнен атомарно. Этот максимум называется {PIPE_BUF}. Этот том стандарта IEEE Std 1003.1-2001 не говорит о том, являются ли запросы записи для более чем {PIPE_BUF} байтов атомарными, но требует, чтобы записи {PIPE_BUF} или меньше байтов были атомарными.
[...]
Таким образом, в принципе, вы должны заблокировать одновременные записи, иначе ваши записанные данные могут смешаться и выйти из строя (даже при одной записи), или у вас может быть несколько записей, перезаписывающих друг друга. Однако есть исключение - если вы сдадите O_APPEND
Ваши записи будут эффективно атомарными:
Если установлен флаг O_APPEND флагов состояния файла, то смещение файла должно быть установлено до конца файла перед каждой записью, и между изменением смещения файла и операцией записи не должно происходить никакой промежуточной операции изменения файла.
Хотя это не обязательно атомарно по отношению кO_APPEND
пишет или одновременно читает, если все авторы используют O_APPEND
и вы как-то синхронизируете перед выполнением read
, ты должен быть в порядке.
write
(а также writev
тоже) гарантия атомности.
Это означает, что если два потока или процесса пишут одновременно, у вас нет гарантии, какой из них пишет первым. Но у вас есть гарантия, что все, что находится в одном системном вызове, не будет смешано с данными другого.
Поскольку он всегда будет работать правильно, но не обязательно так, как вы ожидаете (если вы предполагаете, что процесс A предшествует процессу B).
Конечно, ядро будет обрабатывать его правильно, для представления ядра о корректности - что по определению правильно.
Если у вас есть набор взаимодействующих флокеров, вы можете использовать ядро, чтобы поставить всех в очередь. Но помните, что flock не имеет ничего общего с вводом / выводом: он не остановит запись файла кем-то другим. В лучшем случае это будет мешать другим флокерам.
Да, конечно, это будет работать правильно. Это не приведет к краху ОС или процесса.
Имеет ли это какой-либо смысл, зависит от того, как написаны приложения и какова цель файла.
Если файл открывается всеми процессами только для добавления, каждый процесс (условно) выполняет атомарный поиск до конца перед каждой записью; они гарантированно не перезаписывают данные друг друга (но, конечно, порядок является недетерминированным).
В любом случае, если вы используете библиотеку, которая потенциально разделяет одну логическую запись на несколько системных вызовов записи, ожидайте проблем.
write()
, writev()
, read()
, readv()
может генерировать частичные записи / чтения, когда объем передаваемых данных меньше, чем было запрошено.
Цитирование справочной страницы Linux для writev()
:
Обратите внимание, что для успешного вызова не является ошибкой передачи меньшего количества байтов, чем запрошено
Цитирую справочную страницу POSIX:
Если write() прерывается сигналом после успешной записи некоторых данных, он возвращает количество записанных байтов.
AFAIU, O_APPEND
не помогает в этом отношении, потому что это не предотвращает частичные записи: это только гарантирует, что все записанные данные будут добавлены в конец файла.
Смотрите этот отчет об ошибках в ядре Linux:
Процесс пишет сообщения в файл. [...] записи [...] можно разделить на две части. [...] Таким образом, если поступает сигнал [...], запись прерывается. [...] это совершенно корректное поведение в отношении спецификаций (POSIX, SUS,...)
FIFOs и PIPE пишет меньше, чем PIPE_MAX
однако гарантированно будут атомными.