Несколько процессов пишут в один файл

Обновлено: 05.07.2024

Рассмотрим случай, когда несколько процессов открывают один и тот же файл и добавляют к нему. O_APPEND гарантирует, что поиск конца файла, а затем начало операции записи является атомарным. Таким образом, несколько процессов могут присоединяться к одному и тому же файлу, и ни один процесс не будет перезаписывать любые записи других процессов, поскольку каждый размер записи равен <= PIPE_BUF.

Я написал тестовую программу, в которой несколько процессов открываются и записываются в один и тот же файл ( write(2) ). Я уверен, что каждый размер записи составляет > PIPE_BUF (4k). Я ожидал увидеть случаи, когда процесс перезаписывает чужие данные. Но этого не происходит. Я тестировал с разными размерами записи. Это просто удача или есть причина, почему этого не происходит? Моя конечная цель - понять, нужно ли координировать свои записи несколько процессов, добавляемых к одному файлу.

Вот полная программа. Каждый процесс создает внутренний буфер, заполняет все значения с помощью rank , открывает файл и записывает на него.

Технические характеристики: OpenMPI 1.4.3 на Opensuse 11.3 64-бит

Скомпилирован как: mpicc -O3 test.c, запускается как: mpirun -np 8./a.out

Атомарность записи меньше PIPE_BUF применяется только к трубам и FIFO. Для записи файлов POSIX говорит:

В этом томе POSIX.1-2008 не указано поведение одновременных записывает в файл из нескольких процессов. Приложения должны использовать некоторые форма управления concurrency.

. это означает, что вы сами по себе - разные UNIX-понравятся, дадут разные гарантии.

Во-первых, O_APPEND или эквивалентный FILE_APPEND_DATA в Windows означает, что приращения максимального размера файла (длина файла) являются atomic в параллельных сценариях, и это на любую сумму, а не только на PIPE_BUF. Это гарантируется POSIX, и Linux, FreeBSD, OS X и Windows реализуют его правильно. Samba также реализует его правильно, NFS до v5 не делает, поскольку ему не хватает возможности форматирования каналов для атомарного добавления. Поэтому, если вы откроете свой файл только с помощью append-only, одновременная запись не будет разорваться по отношению друг к другу на какой-либо основной ОС, если не задействована NFS.

Все следующие функции должны быть атомарными по каждому другие в эффектах, указанных в POSIX.1-2008, когда они работают регулярные файлы или символические ссылки. [много функций]. read(). write(). Если каждый из двух потоков вызывает одну из этих функций, каждый вызов должен либо увидеть все указанные эффекты другого вызова, либо ни один из них. [Источник]

Писания могут быть сериализованы по отношению к другим чтениям и записи. Если read() данных файла может быть доказано (каким-либо образом), чтобы произойти после write() данных, он должен отражать, что write(), даже если вызовы производятся различными процессами. [Источник]

В этом томе POSIX.1-2008 не указано поведение одновременных записывает в файл из нескольких процессов. Приложения должны использовать некоторые форма управления concurrency. [Источник]

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

Менее безопасным, но все же допускаемым интерпретацией может быть то, что чтение и запись только сериализации друг с другом между потоками внутри одного и того же процесса, а между процессами записи сериализуются только для чтения (т.е. последовательный последовательный порядок ввода/вывода между потоками в процессе, но между процессами i/o - только получение-релиз).

Конечно, только потому, что стандарт требует этой семантики, это не значит, что реализации соответствуют, хотя на самом деле FreeBSD с ZFS ведет себя отлично, очень недавняя Windows (10.0.14393) с NTFS ведет себя отлично, а последние Linuxes с ext4 ведут себя правильно, если O_DIRECT включен. Если вы хотите получить более подробную информацию о том, насколько хорошо основные ОС и системы регистрации соответствуют стандарту, см. этот ответ

Я где-то читал, что писать (добавлять) в файл из разных процессов можно, при этом гарантируется атомарность записи. Т.е. содержимое буфера, переданное в write появится в файле полностью, не прерываемое записями из других процессов.

Безопасно ли таким образом писать лог-файл из разных процессов?






>Т.е. содержимое буфера, переданное в write появится в файле полностью

write даже без конкурентного доступа не может гарантировать, что весь буфер будет передан. Приходится смотреть на возврат функции, и если надо ещё раз вызывать write.


>Если файл открыт с O_APPEND, то гарантируется


>Если файл открыт с O_APPEND, то гарантируется

А вы умеете пудрить мозги. :) Все правильно, перемещение указателя и запись будут осуществлены атомарно. Но факт атомарности задания смещения и ОДНОЙ операции записи ещё не гарантирует запись ВСЕГО блока данных, который будет передан функции write.

> А вы умеете пудрить мозги. :)

Мы просто о разных вещах говорим :-)

Но факт атомарности задания смещения и ОДНОЙ операции записи ещё не гарантирует запись ВСЕГО блока данных, который будет передан функции write

Естественно. ENOSPC/EIO/EFBIG и прочие прелести никто не отменял. Идея в том, что ситуации, когда первый процесс сделал seek, в это время второй записал данные и только после этого первый записал свои данные, перетирая данные второго процесса, быть не должно.


>ENOSPC/EIO/EFBIG и прочие прелести никто не отменял

Это не самые главные прелести. А вот EINTR уже интереснее будет.

Если внимательно посмотреть, что говорил ТС:

Т.е. содержимое буфера, переданное в write появится в файле ПОЛНОСТЬЮ, не прерываемое записями из других процессов.

А вот этого O_APPEND гарантировать не сможет.

> А вот EINTR уже интереснее будет.

Ну как сказать. Если верить документации, то EINTR возвращается, если the call was interrupted by a signal *before* any data was written.

не прерываемое записями из других процессов


Ну как сказать. Если верить документации, то EINTR возвращается, если the call was interrupted by a signal *before* any data was written.

> и тут внезапно пришел сигнал.

Если мы пишем в сокет/пайп, то да, такая ситуация может иметь место. Но ни сокет, ни пайп не являются seekable devices, и O_APPEND к ним неприменим.


>В том-то и дело, что сигнал не прервет запись.

Такая ситуация может произойти и без сигнала. Просто драйвер может и не захотеть принять за один вызов syscall весь блок данных. По крайней мере на это точно не надо рассчитывать. Это обычно связано с тем, что могут использоваться промежуточные буферы ограниченного размера. Попробуй за один заход затолкнуть гигабайт данных. Наверняка будет принято на запись лишь часть этих данных, для записи остального надо будет повторно вызывать write.

>ПисАть в файл

Для нача́ла научи́сь пра́вильно писа́ть ударе́ния, а пото́м уж переходи́ к за́писи в фа́йлы.

> Попробуй за один заход затолкнуть гигабайт данных.

Но на самом деле размер ограничен размерностью типа ssize_t.

Попробуй за один заход затолкнуть гигабайт данных.

Полтора гига тоже за раз записалось :-)

у меня сдохло на 2 гигабайтах

Expected to write: 6442450944, written: 2147479552


> Безопасно ли таким образом писать лог-файл из разных процессов?


Похоже я действительно был не прав. Я все напутал :(((((. Ограниченность буфера может иметь отношение только к read (и то отчасти). write-у они никак не мешают. Единственная причина, по которой write вынужден вернуть меньшее количество байт, это возникший сигнал. При этом если не было записано ни одного байта, то вернется EINTR, в противном случае вернется записанное число байт. SA_RESTART должен решить проблему.

Правда мне совсем не понятно по части атомарности. Второй перезапуск и последующие так же будут одной атомарной операцией, или как?

Так же я понимаю что write может вернуть меньшее количество байт, чем отправлялось при записи. При этом как я понял не сказано, что это может быть только по причине сигнала. Драйверу может вернуть меньше, если ему так хочется, по каким-то своим неведомым причинам.


В стандарте сказано об атомарности МЕЖДУ изменением и началом записи. Не уверен что делает саму операцию записи всего блока полностью атомарной.


> Правда мне совсем не понятно по части атомарности. Второй перезапуск и последующие так же будут одной атомарной операцией, или как?

Насколько я понимаю, увеличение размера файла происходит _до_ начала write, так что если ты умудришься поймать сигнал в середине write, в файле просто образуется дырка. Кто знает внутренности ядра - поправьте, если что.

> write-у они никак не мешают. Единственная причина, по которой write вынужден вернуть меньшее количество байт, это возникший сигнал.

write может вернуть меньшее количество байт, чем отправлялось при записи. При этом как я понял не сказано, что это может быть только по причине сигнала.

Так ты определись )))


>Так ты определись )))

В общем так. Я не вижу других причин возвращать драйверу меньшее количество байт, кроме прихода сигнала. Но как я понял ничто не запрещает вернуть меньшее количество и без сигнала.

> увеличение размера файла происходит _до_ начала write

Совсем не обязательно. Оба действия происходят до возврата из вызова, но какое первым, а какое вторым - целиком и полностью зависит от реализации драйвера. Но в любом случае проверка на сигнал происходит при переключении контекстов ядро-пользователь (помимо передачи управления шедулером). Сильно сомневаюсь, что ядро будет обрабатывать сигнал, пока мячик на его стороне поля.

Зачем знать внутренности ядра? достаточно знать man.

man 2 write

If the file was open(2)ed with O_APPEND, the file offset is first set to the end of the file before writing. The adjustment of the file offset and the write operation are performed as an atomic step.

> ничто не запрещает вернуть меньшее количество и без сигнала.

Т.е. ты всё-таки вернулся к своей прежней позиции:

write даже без конкурентного доступа не может гарантировать, что весь буфер будет передан.

тут кто-нибудь man читает вообще?

man 2 write

The number of bytes written may be less than count if, for example, there is insufficient space on the underlying physical medium, or the RLIMIT_FSIZE resource limit is encountered (see setrlimit(2)), or the call was interrupted by a signal handler after having written less than count bytes. (See also pipe(7).)

> достаточно знать man.

Еще б неплохо понимать, что в нём пишут. ))


> увеличение размера файла происходит _до_ начала write

>целиком и полностью зависит от реализации драйвера.

тебе надо писателем фантастом работать, но лучше попробовать работать читателем манов.


>The number of bytes written may be less than count if, for example.

Думаю здесь ключевое слово for example

всё понятно, имя ты себе точно подобрал


>> ничто не запрещает вернуть меньшее количество и без сигнала.

Т.е. ты всё-таки вернулся к своей прежней позиции:

Да. У меня по прежнему осталось сомнение, что O_APPEND делает всю операцию записи полностью атомарной.

Не я, а tailgunner


сам как думаешь? )

Насколько я понимаю, увеличение размера файла происходит _до_ начала write, так что если ты умудришься поймать сигнал в середине write, в файле просто образуется дырка.

Спасибо за скальп. Я повешу его перед вигвамом, а ты пока погуляй и выучи английский.

> Да. У меня по прежнему осталось сомнение, что O_APPEND делает всю операцию записи полностью атомарной.


> Оба действия происходят до возврата из вызова, но какое первым, а какое вторым - целиком и полностью зависит от реализации драйвера.

Какого драйвера? Драйвера диска? Точно нет. Драйвера ФС? Тоже нет вряд ли, потому что этот порядок продиктован семантикой.


>> увеличение размера файла происходит _до_ начала write

Ты точно прочитал то, что я написал?


Я бы все таки посоветовал TC использовать flock в любом случае и не искать приключений на свою задницу.


>Ты точно прочитал то, что я написал?

Насколько я понимаю, увеличение размера файла происходит _до_ начала write, так что если ты умудришься поймать сигнал в середине write, в файле просто образуется дырка. Кто знает внутренности ядра - поправьте, если что.

> Драйвера ФС? Тоже нет вряд ли

Именно фс. Кто у нас еще отвечает за размер файла? ))

этот порядок продиктован семантикой.


И как же он её диктует? ))

> при получении сигнала будет записано столько байт, сколько успели

Я создаю систему, в которой несколько подчиненных процессов взаимодействуют через доменные сокеты unix, и они пишут в тот же файл в то же время. Я никогда не изучал файловые системы или эту конкретную файловую систему (ext4), но кажется, что здесь может быть какая-то опасность.

каждый процесс записывает в непересекающееся подмножество выходного файла (т. е. в записываемых блоках нет перекрытия). Например, P1 записывает только первые 50% файла и P2 записывает только до вторых 50%. Или, может быть, P1 пишет только нечетные блоки, в то время как P2 записывает четные блоки.

безопасно ли есть P1 и P2 (выполняется одновременно в отдельных потоках) запись в один и тот же файл без использования блокировки? Другими словами, неявно ли файловая система накладывает какую-то блокировку?

Примечание: к сожалению, я не могу выводить несколько файлов и присоединяться к ним позже.

Примечание: мое чтение с момента публикации этого вопроса не согласуется с единственным опубликованным ответом ниже. Все, что я читал, говорит о том, что я хочу делать хорошо, в то время как респондент ниже настаивает на том, что я делаю небезопасно, но я не могу различить описанную опасность.

то, что вы делаете, кажется совершенно нормальным, если вы используете POSIX "raw" IO syscalls, такие как read (), write (), lseek() и так далее.

Если вы используете c stdio (fread (), fwrite () и friends) или какую-либо другую библиотеку языковой среды выполнения, которая имеет свою собственную буферизацию пользовательского пространства, то ответ "Tilo" релевантен, в том, что из-за буферизации, которая в некоторой степени находится вне вашего контроля, различные процессы могут перезаписывать данные друг друга.

блокировка ОС Wrt, в то время как POSIX утверждает, что запись или чтение меньше размера PIPE_BUF являются атомарными для некоторых специальных файлов (труб и FIFO), нет такой гарантии для обычных файлов. На практике я думаю, что, скорее всего, IO внутри страницы являются атомарными, но такой гарантии нет. ОС выполняет внутреннюю блокировку только в той мере, в какой это необходимо для защиты собственных внутренних структур данных. Для сериализации доступа к файлам можно использовать блокировки файлов или какой-либо другой механизм межпроцессной связи. Но, все это относится только к вам, у вас есть несколько процессов, выполняющих ввод-вывод в одну и ту же область файла. В вашем случае, поскольку ваши процессы делают IO для разъединения разделов файла, ничто из этого не имеет значения, и вы должны быть в порядке.

нет, как правило, это небезопасно делать!

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

лучше иметь один выходной файл для каждого процесса и форматировать эти файлы с меткой времени и идентификатором процесса в начале строки, чтобы вы могли позже объединить и отсортировать эти выходные файлы в автономном режиме.

совет: проверьте формат файла файлов журнала веб-сервера - они сделаны с отметкой времени в начале строки, поэтому они могут быть позже объединены и отсортированы.

процессы UNIX используют определенный / фиксированный размер буфера при открытии файлов (например, 4096 байт) для передачи данных в файл на диске и из него. Как только буфер записи заполнен, процесс сбрасывает его на диск - это означает: он пишет полный полный буфер на диск! Обратите внимание, что это происходит, когда буфер полон! -- нет, когда есть конец линии! Это означает, что даже для одного процесса, который записывает в файл текстовые данные, ориентированные на строки, эти строки обычно вырезаются где-то посередине во время сброса буфера. Только в конце, когда файл закрывается после записи, можно предположить, что файл содержит полные строки!

Так в зависимости от когда ваш процесс решит потопить их буферы, они пишут в разное время в файл - например, порядок не является детерминированным / предсказуемым, когда буфер сбрасывается в файл, вы можете Не предполагайте, что он будет писать только полные строки -- например,обычно он будет писать частичные строки, тем самым испортив выход, если несколько процессов очищают свои буферы без синхронизации.

цитата:

операционные системы Unix (включая Linux и Mac OS X от Apple, иногда называют Дарвином)обычно не автоматически блокировать открытые файлы или запуск программ. несколько видов файл-фиксируя механизмов доступно в различных вариантах Unix и во многих операционных системах поддержите больше чем один вид для совместимость. Два наиболее распространенных механизмы-fcntl (2) и flock(2). Третий такой механизм lockf (3), который может быть отдельным или может быть реализован с использованием первые два примитива.

вы должны использовать flock, или мьютексы синхронизировать процессы и убедитесь, что только один из них может писать в файл одновременно.

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

вот связанный пост: (отметьте ответ Байера! принятый ответ не является правильным/уместным.)

в комментарии Вы сказали, что хотите записать двоичные блоки данных фиксированного размера из разных процессов в один и тот же файл.

только в том случае, если размер вашего блока точно соответствует размеру файла-буфера системы, это может работать!

убедитесь, что ваша фиксированная длина блока является именно файловым буфером системы размер. В противном случае вы попадете в ту же ситуацию, что и с незавершенными строками. например, если вы используете 16K блоков, а система использует 4K блоков, то в целом вы увидите 4K блоков в файле в кажущемся случайном порядке - нет никакой гарантии, что вы всегда будете видеть 4 блока подряд из одного и того же процесса


Эта статья не для матёрых укротителей Python’а, для которых распутать этот клубок змей — детская забава, а скорее поверхностный обзор многопоточных возможностей для недавно подсевших на питон.

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

Python — очаровательный язык программирования. В нем прекрасно сочетается множество парадигм программирования. Большинство задач, с которыми может встретиться программист, решаются здесь легко, элегантно и лаконично. Но для всех этих задач зачастую достаточно однопоточного решения, а однопоточные программы обычно предсказуемы и легко поддаются отладке. Чего не скажешь о многопоточных и многопроцессных программах.

Многопоточные приложения

В Python есть модуль threading, и в нем есть все, что нужно для многопоточного программирования: тут есть и различного вида локи, и семафор, и механизм событий. Один словом — все, что нужно для подавляющего большинства многопоточных программ. Причем пользоваться всем этим инструментарием достаточно просто. Рассмотрим пример программы, которая запускает 2 потока. Один поток пишет десять “0”, другой — десять “1”, причем строго по-очереди.


Никакой магии и voodoo-кода. Код четкий и последовательный. Причем, как можно заметить, мы создали поток из функции. Для небольших задач это очень удобно. Этот код еще и достаточно гибкий. Допустим у нас появился 3-й процесс, который пишет “2”, тогда код будет выглядеть так:


Мы добавили новое событие, новый поток и слегка изменили параметры, с которыми
стартуют потоки (можно конечно написать и более общее решение с использованием, например, MapReduce, но это уже выходит за рамки этой статьи).
Как видим по-прежнему никакой магии. Все просто и понятно. Поехали дальше.

Global Interpreter Lock

Существуют две самые распространенные причины использовать потоки: во-первых, для увеличения эффективности использования многоядерной архитектуры cоврменных процессоров, а значит, и производительности программы;
во-вторых, если нам нужно разделить логику работы программы на параллельные полностью или частично асинхронные секции (например, иметь возможность пинговать несколько серверов одновременно).

В первом случае мы сталкиваемся с таким ограничением Python (а точнее основной его реализации CPython), как Global Interpreter Lock (или сокращенно GIL). Концепция GIL заключается в том, что в каждый момент времени только один поток может исполняться процессором. Это сделано для того, чтобы между потоками не было борьбы за отдельные переменные. Исполняемый поток получает доступ по всему окружению. Такая особенность реализации потоков в Python значительно упрощает работу с потоками и дает определенную потокобезопасность (thread safety).

Но тут есть тонкий момент: может показаться, что многопоточное приложение будет работать ровно столько же времени, сколько и однопоточное, делающее то же самое, или за сумму времени исполнения каждого потока на CPU. Но тут нас поджидает один неприятный эффект. Рассмотрим программу:


Эта программа просто пишет в файл миллион строк “1” и делает это за

0.35 секунды на моем компьютере.

Рассмотрим другую программу:


Эта программа создает 2 потока. В каждом потоке она пишет в отдельный файлик по пол миллиона строк “1”. По-сути объем работы такой же, как и у предыдущей программы. А вот со временем работы тут получается интересный эффект. Программа может работать от 0.7 секунды до аж 7 секунд. Почему же так происходит?

Это происходит из-за того, что когда поток не нуждается в ресурсе CPU — он освобождает GIL, а в этот момент его может попытаться получить и он сам, и другой поток, и еще и главный поток. При этом операционная система, зная, что ядер много, может усугубить все попыткой распределить потоки между ядрами.

UPD: на данный момент в Python 3.2 существует улучшенная реализация GIL, в которой эта проблема частично решается, в частности, за счет того, что каждый поток после потери управления ждет небольшой промежуток времени до того, как сможет опять захватить GIL (на эту тему есть хорошая презентация на английском)

«Выходит на Python нельзя писать эффективные многопоточные программы?», — спросите вы. Нет, конечно, выход есть и даже несколько.

Многопроцессные приложения

Для того, чтобы в некотором смысле решить проблему, описанную в предыдущем параграфе, в Python есть модуль subprocess. Мы можем написать программу, которую хотим исполнять в параллельном потоке (на самом деле уже процессе). И запускать ее в одном или нескольких потоках в другой программе. Такой способ действительно ускорил бы работу нашей программы, потому, что потоки, созданные в запускающей программе GIL не забирают, а только ждут завершения запущенного процесса. Однако, в этом способе есть масса проблем. Основная проблема заключается в том, что передавать данные между процессами становится трудно. Пришлось бы как-то сериализовать объекты, налаживать связь через PIPE или друге инструменты, а ведь все это несет неизбежно накладные расходы и код становится сложным для понимания.

Здесь нам может помочь другой подход. В Python есть модуль multiprocessing. По функциональности этот модуль напоминает threading. Например, процессы можно создавать точно так же из обычных функций. Методы работы с процессами почти все те же самые, что и для потоков из модуля threading. А вот для синхронизации процессов и обмена данными принято использовать другие инструменты. Речь идет об очередях (Queue) и каналах (Pipe). Впрочем, аналоги локов, событий и семафоров, которые были в threading, здесь тоже есть.

Кроме того в модуле multiprocessing есть механизм работы с общей памятью. Для этого в модуле есть классы переменной (Value) и массива (Array), которые можно “обобщать” (share) между процессами. Для удобства работы с общими переменными можно использовать классы-менеджеры (Manager). Они более гибкие и удобные в обращении, однако более медленные. Нельзя не отметить приятную возможность делать общими типы из модуля ctypes с помощью модуля multiprocessing.sharedctypes.

Еще в модуле multiprocessing есть механизм создания пулов процессов. Этот механизм очень удобно использовать для реализации шаблона Master-Worker или для реализации параллельного Map (который в некотором смысле является частным случаем Master-Worker).

Из основных проблем работы с модулем multiprocessing стоит отметить относительную платформозависимость этого модуля. Поскольку в разных ОС работа с процессами организована по-разному, то на код накладываются некоторые ограничения. Например, в ОС Windows нет механизма fork, поэтому точку разделения процессов надо оборачивать в:


Впрочем, эта конструкция и так является хорошим тоном.

Что еще .

Для написания параллельных приложений на Python существуют и другие библиотеки и подходы. Например, можно использовать Hadoop+Python или различные реализации MPI на Python (pyMPI, mpi4py). Можно даже использовать обертки существующих библиотек на С++ или Fortran. Здесь можно было упомянуть про такие фреймфорки/библиотеки, как Pyro, Twisted, Tornado и многие другие. Но это все уже выходит за пределы этой статьи.

Если мой стиль вам понравился, то в следующей статье постараюсь рассказать, как писать простые интерпретаторы на PLY и для чего их можно применять.

Есть программа (Ubuntu 12.04 LTS, одноядерный процессор):Идея состоит в том, чтобы открыть файл для записи, а затем перейти к форку.Положение, в котором будет рекордный итог для обоих процессов.Странно то, что если вы запускаете программу, она выводится в файл "res" не постоянно: я разозлился тогда на 34, потом на 4, затем на 3. Вопрос в том, почему такой вывод?(В конце концов, если позиция разделяется, то вывод должен быть либо 34, либо 43).По моему подозрению, процесс прерывается в функции write, когда он находит позицию для записи.

Когда вы запускаете несколько процессов с помощью fork (), невозможно определить, в каком порядке они будут выполняться.Решать должен планировщик операционных систем.Таким образом, запись нескольких процессов в один и тот же файл - это путь к катастрофе.Что касается вопроса, почему иногда пропускается одно из двух чисел: write сначала записывает данные, а затем увеличивает указатель файла.Я думаю, что вполне возможно, что управление потоком изменяется именно в этот момент, так что второй поток пишет до обновления позиции файла.Таким образом, он перезаписывает данные, которые только что написал другой процесс.

Я запускаю вашу программу несколько раз, и результат «34» или «43».Итак, я написал сценарий оболочкии запустите вашу программу 500 раз.Как мы можем видеть, он получает «3» или «4» несколько раз (примерно 20 раз из 500)。 Как мы можем это объяснить?Ответ таков: когда мы выполняем fork () дочерним процессом, дочерний процесс использует то же описание файла и структуру состояния файла (которая имеет текущее смещение файла).Обычно процесс получает смещение = 0 первым и записывает первый байт, а смещение = 1, другой процесс получает смещение = 1, и он записывает второй байт.Но иногда, если родительский процесс получает смещение = 0 из структуры состояния файла, а дочерний процесс получает смещение = 0 одновременно , процесс записывает первый байт, а другой перезаписывает первый байт.Результатом будет «3» или «4» (зависит от того, кто из родителей первым пишет или потомок).Потому что они оба пишут первый байт файла.Вилка и офсет, смотрите это

Вы уверены, что вам нужна функция fork ()?fork () создает другой процесс с разным пространством памяти (файловые дескрипторы и т. д.).Может быть, pthreads подойдет вам?В случае с pthreads вы будете использовать один и тот же fd для всех процессов.Но в любом случае, вы должны подумать об использовании мьютексов в вашем проекте.

Вот что я думаю, что происходит.Вы ---- +: = 0 =: + ---- файл, а вы ---- +: = 1 =: + ----Родитель запускается первым (аналогичные вещи могут произойти, если ребенок бежит первым)Родитель пишет ---- +: = 2 =: + ---- и выходитРодитель был процессом управления для терминала, поэтому ядро ​​отправляет сигнал SIGHUP всем членам группы переднего плана.Действие по умолчанию ---- +: = 3 =: + ---- - завершить процесс, чтобы ребенок молча умерПростой способ проверить это - добавить сон:Вы увидите, что дочерний процесс мгновенно умирает: запись никогда не выполняется .Еще один способ проверить это - проигнорировать ---- +: = 5 =: + ---- , перед тем как разветвляться :Вы увидите, что теперь процесс записывает обе цифры в файл.Переписывающая гипотеза маловероятна.После ---- +: = 7 =: + ---- оба процесса совместно используют ссылку на один и тот же дескриптор файла в общесистемной таблице, которая также содержит смещение файла .

Читайте также: