Интересующий вопрос. Как упоминается в другом ответе, у вас есть состояние гонки, и я в этом уверен. Фактически, у вас есть условие гонки в обоих случаях, но в первом вам просто повезло, этого не происходит, потому что, возможно, ваши файлы маленькие и могут быть прочитаны перед входом в следующую командную строку. Позвольте мне объяснить.
Итак, немного фона первый:
cat
открывает каждый файл, вы кормите его в качестве аргумента последовательно, выводит его на выход, а затем закрывает файл и переходит к следующему файлу. Точные данные о том, будет ли cat
каждый раз открывать каждый файл или сначала открывать их все, а затем записывать каждый файл, могут быть разными, но это не относится к обсуждению. В обоих случаях у вас будет состояние гонки
- Сценарий
open(2)
будет блокироваться на FIFO/pipe, пока не откроется другой конец. Например, если процесс pid1
открывает FIFO для чтения, open(2)
будет блокироваться, пока, скажем, pid2
не откроет FIFO для записи. Другими словами, открытие FIFO, у которого нет активных читателей или писателей, неявно синхронизирует оба процесса и гарантирует, что процесс не будет читаться из канала, у которого пока нет писателя, или что писатель не будет писать в канал, у которого пока нет читателя , Но, как мы увидим, это будет проблематично.
Что на самом деле происходит
Когда вы сделаете это:
cat xaa > p1
cat xab > p1
cat xac > p1
Вещи очень медленно, потому что люди медленно. После ввода первой строки cat
открывает p1
для записи. Другой cat
заблокирован при открытии его для чтения (или, может быть, еще нет, но предположим, что это так). Как только оба процесса cat
открывают p1
- один для записи, другой для чтения - данные начинают течь.
И тогда, прежде чем вы даже есть шанс войти в следующую командную строку (cat xab >p1
), весь файл проходит через трубу и все довольны - процесс cat
читатель видит конец файла на трубе, вызывает close(2)
, писатель cat
заканчивает запись файла и закрывает p1
. Читатель cat
переходит к следующему файлу (это p1
), открывает его и блокирует, потому что ни один активный писатель еще не открыл фиолетовый.
Затем, вы, замедленный человек, введите следующую командную строку, которая заставляет другой процесс записи cat
открыть FIFO, который разблокирует другой cat
, который ждет открытия для чтения, и все повторится. И снова для третьей командной строки.
Когда вы помещаете все в одну строку в оболочку, все происходит слишком быстро.
Проповедуем три вызова 3 cat
. Назовите это cat1
, cat2
и cat3
:
cat1 xaa > p1; cat2 xab > p1; cat3 xac > p1
Оболочка выполняет каждую команду последовательно, ожидая предыдущей команды, чтобы закончить, прежде чем перейти к следующему.
Однако, это может быть просто так, что cat1
закончил писать все для p1
и выходов, оболочка переходит к cat2
, который открывает FIFO и начинает писать снова содержимое p1
, и читатель cat
не было шанс закончить чтение того, что написал cat1
, и теперь внезапно читатель cat
«думает», что он все еще читает из первого файла (первый p1
), но в какой-то момент он начинает считывать данные, которые cat2
начал толкать в трубы (как будто это было в первых p1
). Он не знает, что первая «копия» данных завершена, если cat2
быстрее и открывает FIFO, прежде чем читатель cat
закончит читать то, что написал cat1
.
Да, тонкий, но это именно то, что происходит.
Тогда, конечно, вход в конце концов приходит к концу, и читатель cat
будет думать, что первый p1
делается и переходит к следующему p1
, открывая его и ждет следующего писателя, чтобы открыть его. Но никогда не будет следующего писателя! Он блокируется навсегда, и весь трубопровод застопорился навсегда.
Как исправить это
Раствор в другой ответ решает эту проблему. Вы упомянули в комментариях, что этого может быть недостаточно для вас, потому что вы не контролируете, когда и как открывается новый писатель, и использует этот канал.
Поэтому я предлагаю вместо этого:
- Создать постоянный процесс писателя, который только поддерживает буфер FIFO открыт для записи, даже если он никогда не будет на самом деле писать. Это просто, чтобы убедиться, что нет окна времени, когда никакие авторы не активны, и читатель пытается прочитать. Для этого просто
cat
стандартный ввод для p1
в фоновом режиме: cat >p1 &
. Когда вы закончите, убейте фоновое задание.
- Откройте трубу только один раз в процессе считывания. Это можно сделать либо с помощью
cat p1 | tee >(sha1sum ...)
, либо с использованием метода, предложенного в другом ответе (tee >(...) <p1
). В конце концов, открытие FIFO однажды должно быть достаточно независимо от того, насколько сложна ваша система; FIFO по своей природе всегда дают вам данные в первом порядке.
Сохраните фоновый рисунок cat
писателя, пока вы знаете, что есть вероятность появления новых файлов/новых авторов, открывающих FIFO и использующих его. Не забудьте завершить фоновое задание, когда вы знаете, что вход окончен.
Хорошая мысль. Благодарю. Но пример, который я дал, является лишь примером, и я, вероятно, не буду иметь контроль над концом, который передает канал; Я мог бы застрять в файлах, проходящих через один за раз. Все еще озадачен, почему он работает, когда труба подается по одной линии за раз, но не удается, когда каждая кошка встречается на одной линии. Кажется, что два должны быть эквивалентными. – SixDegrees
@SixDegrees Не полагайтесь на это. За исключением очень простых случаев, работать с трубами довольно сложно, и вам действительно нужно понять, что происходит под капотом, чтобы иметь шанс на это. Рассмотрим трубы - отличный источник тупиков. :) – lcd047
@SixDegrees Действительно, это состояние гонки - оно тонкое, но оно есть. Этот ответ описывает, как исправить это довольно хорошо; мои попытки объяснить, почему это происходит. Если вы не можете контролировать, как данные поступают в FIFO, см. Мое предложение в конце моего ответа - возможно, это сработает для вас. –